dsh-zen-remote 1.1.13 → 1.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +3 -2
  2. package/README.zh-CN.md +3 -2
  3. package/docs/interface.md +46 -3
  4. package/lib/client.js +3421 -103
  5. package/lib/index.js +13 -0
  6. package/lib/index.js.map +1 -1
  7. package/lib/share-export.js +347 -0
  8. package/lib/share-export.js.map +1 -0
  9. package/lib/types/client/MobileAttachButton.d.ts +8 -1
  10. package/lib/types/client/MobileAttachButton.d.ts.map +1 -1
  11. package/lib/types/client/MobileAttachChips.d.ts.map +1 -1
  12. package/lib/types/client/MobileSessionHeader.d.ts +7 -8
  13. package/lib/types/client/MobileSessionHeader.d.ts.map +1 -1
  14. package/lib/types/client/MobileSessionInfo.d.ts.map +1 -1
  15. package/lib/types/client/effects/keyboard-avoid.d.ts.map +1 -1
  16. package/lib/types/client/effects/keyboard-guard.d.ts +16 -11
  17. package/lib/types/client/effects/keyboard-guard.d.ts.map +1 -1
  18. package/lib/types/client/host-attach.d.ts +38 -0
  19. package/lib/types/client/host-attach.d.ts.map +1 -0
  20. package/lib/types/client/index.d.ts.map +1 -1
  21. package/lib/types/client/locales.d.ts +23 -0
  22. package/lib/types/client/locales.d.ts.map +1 -1
  23. package/lib/types/client/share/fetch-share.d.ts +77 -0
  24. package/lib/types/client/share/fetch-share.d.ts.map +1 -0
  25. package/lib/types/client/share/markdown.d.ts +97 -0
  26. package/lib/types/client/share/markdown.d.ts.map +1 -0
  27. package/lib/types/client/share/png-stitch.d.ts +96 -0
  28. package/lib/types/client/share/png-stitch.d.ts.map +1 -0
  29. package/lib/types/client/share/rasterize.d.ts +317 -0
  30. package/lib/types/client/share/rasterize.d.ts.map +1 -0
  31. package/lib/types/client/share/share-card.d.ts +93 -0
  32. package/lib/types/client/share/share-card.d.ts.map +1 -0
  33. package/lib/types/client/share/share-constants.d.ts +27 -0
  34. package/lib/types/client/share/share-constants.d.ts.map +1 -0
  35. package/lib/types/client/share/share-flow.d.ts +76 -0
  36. package/lib/types/client/share/share-flow.d.ts.map +1 -0
  37. package/lib/types/client/share/share-preview.d.ts +11 -0
  38. package/lib/types/client/share/share-preview.d.ts.map +1 -0
  39. package/lib/types/client/share/share-sheet.d.ts +17 -0
  40. package/lib/types/client/share/share-sheet.d.ts.map +1 -0
  41. package/lib/types/client/share/text-layout.d.ts +48 -0
  42. package/lib/types/client/share/text-layout.d.ts.map +1 -0
  43. package/lib/types/client/styles/header.css.d.ts +1 -1
  44. package/lib/types/client/styles/header.css.d.ts.map +1 -1
  45. package/lib/types/client/styles/layout.css.d.ts +1 -1
  46. package/lib/types/client/styles/layout.css.d.ts.map +1 -1
  47. package/lib/types/index.d.ts.map +1 -1
  48. package/lib/types/share-export.d.ts +66 -0
  49. package/lib/types/share-export.d.ts.map +1 -0
  50. package/package.json +7 -2
  51. package/scripts/check-host-shadow.mjs +41 -9
  52. package/scripts/check-keyboard-avoid.mjs +4 -2
  53. package/scripts/check-share-image.mjs +970 -0
  54. package/scripts/check-upload-endpoint.mjs +10 -3
  55. package/src/client/MobileAttachButton.tsx +13 -0
  56. package/src/client/MobileAttachChips.tsx +8 -1
  57. package/src/client/MobileSessionHeader.tsx +90 -30
  58. package/src/client/MobileSessionInfo.tsx +234 -1
  59. package/src/client/effects/keyboard-avoid.ts +14 -7
  60. package/src/client/effects/keyboard-guard.ts +28 -18
  61. package/src/client/host-attach.ts +54 -0
  62. package/src/client/index.tsx +16 -0
  63. package/src/client/locales.ts +46 -0
  64. package/src/client/share/fetch-share.ts +161 -0
  65. package/src/client/share/markdown.ts +609 -0
  66. package/src/client/share/png-stitch.ts +281 -0
  67. package/src/client/share/rasterize.ts +1078 -0
  68. package/src/client/share/share-card.tsx +637 -0
  69. package/src/client/share/share-constants.ts +30 -0
  70. package/src/client/share/share-flow.ts +166 -0
  71. package/src/client/share/share-preview.tsx +337 -0
  72. package/src/client/share/share-sheet.tsx +222 -0
  73. package/src/client/share/text-layout.ts +147 -0
  74. package/src/client/styles/header.css.ts +30 -0
  75. package/src/client/styles/layout.css.ts +5 -2
  76. package/src/index.ts +13 -0
  77. package/src/share-export.ts +431 -0
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Share-card pure template (ticket 03, PLAN §5.1): renders the share-export
3
+ * transcript JSON as one self-styled card — header strip, user bubbles,
4
+ * assistant prose, brand footer.
5
+ *
6
+ * Hard rule for this file: EVERY element carries only an inline `style`, no
7
+ * `className`, no style-module imports. The ticket-04 rasterizer deep-clones
8
+ * this tree into an SVG `<foreignObject>` and paints it as an image — an
9
+ * image loads no stylesheets, so the card must be fully self-describing. The
10
+ * same rule is why wide content (long code lines, pipe tables) renders at
11
+ * natural width with `overflow` left visible: hiding or clipping it here
12
+ * would silently drop it from the exported PNG (adaptive slice width is
13
+ * ticket 04's job; this template just must never lie about content width).
14
+ */
15
+ import type { ReactNode } from 'react';
16
+ /** One exportable content block of a transcript row. */
17
+ export type ShareBlock = {
18
+ kind: 'text';
19
+ text: string;
20
+ } | {
21
+ kind: 'image';
22
+ };
23
+ /** One transcript row; `seq` (log position) doubles as the React key. */
24
+ export interface ShareTurn {
25
+ role: 'user' | 'assistant';
26
+ seq: number;
27
+ blocks: ShareBlock[];
28
+ }
29
+ /** Concrete colors/fonts the template bakes into its inline styles. */
30
+ export interface ShareTheme {
31
+ /** Primary prose color (--dsw-alias-label-primary). */
32
+ text: string;
33
+ /** Header meta / secondary text (--dsw-alias-label-secondary). */
34
+ textSecondary: string;
35
+ /** Footer / caption text (--dsw-alias-label-tertiary). */
36
+ textTertiary: string;
37
+ /** Card background (--dsw-alias-bg-base). */
38
+ cardBg: string;
39
+ /** Code/table block background (--dsw-alias-markdown-code-block). */
40
+ codeBg: string;
41
+ /** User-bubble fill (--dsw-alias-brand-primary). */
42
+ bubbleBg: string;
43
+ /** User-bubble text (--dsw-alias-label-primary-foreground). */
44
+ bubbleText: string;
45
+ /** Brand accent (--dsw-alias-state-business-primary, DeepSeek blue). */
46
+ accent: string;
47
+ /** Hairline color (--dsw-alias-border-l2). */
48
+ border: string;
49
+ /** Sans font stack (--dsw-font-family). */
50
+ fontFamily: string;
51
+ /** Mono font stack (--ds-font-family-code). */
52
+ monoFamily: string;
53
+ }
54
+ /** Light-palette fallback (resolved values from the host theme package). */
55
+ export declare const DEFAULT_SHARE_THEME: ShareTheme;
56
+ /**
57
+ * Read the host theme once (per render call). A raw value that still contains
58
+ * `var(` means the engine returned it un-substituted — treated as missing so
59
+ * the default palette wins over a broken declaration.
60
+ * @returns concrete theme values for this render.
61
+ */
62
+ export declare function readShareTheme(): ShareTheme;
63
+ /** Localizable strings baked into the shared image (via locales `t`). */
64
+ export interface ShareCardCopy {
65
+ /** Header turn count, e.g. "3 轮对话". */
66
+ turnsLabel: (count: number) => string;
67
+ /** Footer attribution, e.g. "由 DeepSeek Harness 生成". */
68
+ generatedBy: string;
69
+ /** Image-attachment placeholder caption. */
70
+ image: string;
71
+ }
72
+ /** ShareCard props. All style decisions are internal; data in, pixels out. */
73
+ export interface ShareCardProps {
74
+ /** Session display title (header strip). */
75
+ title: string;
76
+ /** Optional second header line (agent preset / model). */
77
+ subtitle?: string;
78
+ /** Session creation time (epoch ms); omitted from the header when missing. */
79
+ createdAt?: number;
80
+ /** Transcript rows in conversation order. */
81
+ turns: readonly ShareTurn[];
82
+ /** Logical width in px (default 390; ticket 04 passes wider for wide slices). */
83
+ width?: number;
84
+ /** Localized strings, see {@link ShareCardCopy}. */
85
+ copy: ShareCardCopy;
86
+ }
87
+ /**
88
+ * The share card. A pure template: no hooks, no effects, no context — the
89
+ * same JSX must render identically inside the page (preview) and inside a
90
+ * cloned `<foreignObject>` (rasterize).
91
+ */
92
+ export declare function ShareCard({ title, subtitle, createdAt, turns, width, copy }: ShareCardProps): ReactNode;
93
+ //# sourceMappingURL=share-card.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"share-card.d.ts","sourceRoot":"","sources":["../../../../src/client/share/share-card.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAiB,SAAS,EAAE,MAAM,OAAO,CAAA;AAerD,wDAAwD;AACxD,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAA;AAErB,yEAAyE;AACzE,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,GAAG,WAAW,CAAA;IAC1B,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,UAAU,EAAE,CAAA;CACrB;AAaD,uEAAuE;AACvE,MAAM,WAAW,UAAU;IACzB,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAA;IACZ,kEAAkE;IAClE,aAAa,EAAE,MAAM,CAAA;IACrB,0DAA0D;IAC1D,YAAY,EAAE,MAAM,CAAA;IACpB,6CAA6C;IAC7C,MAAM,EAAE,MAAM,CAAA;IACd,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAA;IACd,oDAAoD;IACpD,QAAQ,EAAE,MAAM,CAAA;IAChB,+DAA+D;IAC/D,UAAU,EAAE,MAAM,CAAA;IAClB,wEAAwE;IACxE,MAAM,EAAE,MAAM,CAAA;IACd,8CAA8C;IAC9C,MAAM,EAAE,MAAM,CAAA;IACd,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAA;IAClB,+CAA+C;IAC/C,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,4EAA4E;AAC5E,eAAO,MAAM,mBAAmB,EAAE,UAYjC,CAAA;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,IAAI,UAAU,CAyB3C;AAED,yEAAyE;AACzE,MAAM,WAAW,aAAa;IAC5B,uCAAuC;IACvC,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IACrC,wDAAwD;IACxD,WAAW,EAAE,MAAM,CAAA;IACnB,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAA;CACd;AAED,8EAA8E;AAC9E,MAAM,WAAW,cAAc;IAC7B,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAA;IACb,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,8EAA8E;IAC9E,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,6CAA6C;IAC7C,KAAK,EAAE,SAAS,SAAS,EAAE,CAAA;IAC3B,iFAAiF;IACjF,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,oDAAoD;IACpD,IAAI,EAAE,aAAa,CAAA;CACpB;AA0BD;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,cAAc,GAAG,SAAS,CAyGvG"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Template constants the share card and its rasterizer MUST agree on
3
+ * (tickets 03/04; the width-lifting formulas in rasterize.ts invert the
4
+ * template's own constraints, so a diverging copy would mis-bucket wide
5
+ * content). Formerly duplicated as literals in share-card.tsx and re-derived
6
+ * in rasterize.ts with a regex lock in the check script; both sides now
7
+ * import this single module, and scripts/check-share-image.mjs asserts its
8
+ * values directly.
9
+ *
10
+ * Hard rule for this file: a pure leaf — no imports, no JSX, no DOM, no
11
+ * module side effects. Node type stripping executes every value import, and
12
+ * both the check script and the client bundler rely on this module being
13
+ * loadable anywhere.
14
+ */
15
+ /** Base logical card width (share-card.tsx default render width). */
16
+ export declare const BASE_CARD_WIDTH = 390;
17
+ /** Widened slice width for wide content (PLAN §5.2 “更大逻辑宽 ~780”). */
18
+ export declare const WIDE_SLICE_WIDTH = 780;
19
+ /**
20
+ * User-bubble width cap relative to its row (`maxWidth` in share-card.tsx,
21
+ * e.g. 0.82 → '82%'). cardWidthForUserBubble inverts this percentage into a
22
+ * division, so the two sides must be the same number.
23
+ */
24
+ export declare const USER_BUBBLE_MAX_RATIO = 0.82;
25
+ /** Horizontal padding inside the user bubble (its `padding` X component). */
26
+ export declare const USER_BUBBLE_PAD_X = 14;
27
+ //# sourceMappingURL=share-constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"share-constants.d.ts","sourceRoot":"","sources":["../../../../src/client/share/share-constants.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,qEAAqE;AACrE,eAAO,MAAM,eAAe,MAAM,CAAA;AAElC,qEAAqE;AACrE,eAAO,MAAM,gBAAgB,MAAM,CAAA;AAEnC;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,OAAO,CAAA;AAEzC,6EAA6E;AAC7E,eAAO,MAAM,iBAAiB,KAAK,CAAA"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Share delivery (ticket 05, PLAN §5.4; ticket 07 adds the single-image
3
+ * path): turn the rasterizer's output into Files named after the session
4
+ * title and hand them to the system share panel via `navigator.share({files})`,
5
+ * falling back to `<a download>` where the Web Share API is missing, refuses
6
+ * the files, or loses the user activation during the multi-second rasterize.
7
+ * Ticket 07 prefers ONE stitched long-image File (`deliverShareImage`) and
8
+ * keeps the per-slice form (`deliverShareImages`) as the fallback for
9
+ * browsers without CompressionStream.
10
+ *
11
+ * Also hosts the filename sanitizer — pure, and pinned by the share-image
12
+ * check script, which imports this module through Node type stripping (so:
13
+ * no JSX imports, browser globals only inside function bodies, constructor
14
+ * fields assigned in the body).
15
+ */
16
+ import type { RasterizedSlice } from './rasterize.ts';
17
+ /** Basename used when the session title cleans down to nothing. */
18
+ export declare const FALLBACK_SHARE_BASE_NAME = "share-card";
19
+ /**
20
+ * Max characters (code points, not UTF-16 units) kept from the title. The
21
+ * `-NN.png` suffix adds 7; 50 + 7 stays far inside every filesystem's 255
22
+ * byte-name limit even once UTF-8 doubles the CJK characters.
23
+ */
24
+ export declare const SHARE_NAME_MAX = 50;
25
+ /**
26
+ * Pure: session title → portable file basename. Path separators, control
27
+ * characters and bidi format characters are removed (a separator in a
28
+ * download name lets a hostile title pick the destination directory; a bidi
29
+ * character visually reorders the name into an extension spoof), whitespace
30
+ * runs fold to one space, the result is length-capped on code points (no
31
+ * splitting surrogate pairs) and falls back to {@link FALLBACK_SHARE_BASE_NAME}
32
+ * when nothing survives.
33
+ */
34
+ export declare function sanitizeShareFileName(title: string): string;
35
+ /**
36
+ * File name of slice {@link index} (0-based): `<base>-NN.png`. Two digits
37
+ * cover the rasterizer's 24-slice ceiling with room to spare and sort
38
+ * lexicographically in every receiver's file list. Legacy multi-file fallback
39
+ * only (ticket 07 stitches first when the browser supports it).
40
+ */
41
+ export declare function shareSliceFileName(base: string, index: number): string;
42
+ /**
43
+ * File name of the stitched single long image (ticket 07): `<base>.png` — no
44
+ * slice suffix, the deliverable is one file.
45
+ */
46
+ export declare function shareImageFileName(base: string): string;
47
+ /** How the images left the page — for status copy and instrumentation. */
48
+ export type ShareDeliveryMethod = 'shared' | 'downloaded' | 'cancelled';
49
+ /** One entry of a {@link downloadBlobSequence}: the bytes and the file name. */
50
+ export interface DownloadEntry {
51
+ blob: Blob;
52
+ name: string;
53
+ }
54
+ /**
55
+ * The `<a download>` fallback, as one paced sequence (review 07+08 cleanup —
56
+ * previously tripled across this module and the debug preview's export):
57
+ * object URL → programmatic anchor click → delayed revoke per entry, with
58
+ * consecutive clicks spaced 300ms apart so browsers do not coalesce or block
59
+ * them. A single entry is simply one click with no pause.
60
+ */
61
+ export declare function downloadBlobSequence(entries: readonly DownloadEntry[]): Promise<void>;
62
+ /**
63
+ * Deliver rasterized slices (legacy multi-file fallback): system share panel
64
+ * when the browser accepts files, downloads otherwise. A share() rejection
65
+ * that is NOT the user's own cancel — classically NotAllowedError, because
66
+ * seconds of rasterizing spent the click's transient activation — falls
67
+ * through to downloads rather than discarding the finished PNGs.
68
+ */
69
+ export declare function deliverShareImages(slices: readonly RasterizedSlice[], title: string): Promise<ShareDeliveryMethod>;
70
+ /**
71
+ * Deliver the stitched single long image (ticket 07): ONE `<base>.png` through
72
+ * the system share panel, or a single download where the Web Share API is
73
+ * missing/refusing — never a file sequence.
74
+ */
75
+ export declare function deliverShareImage(png: Blob, title: string): Promise<ShareDeliveryMethod>;
76
+ //# sourceMappingURL=share-flow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"share-flow.d.ts","sourceRoot":"","sources":["../../../../src/client/share/share-flow.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAErD,mEAAmE;AACnE,eAAO,MAAM,wBAAwB,eAAe,CAAA;AACpD;;;;GAIG;AACH,eAAO,MAAM,cAAc,KAAK,CAAA;AAgBhC;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAc3D;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAEtE;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED,0EAA0E;AAC1E,MAAM,MAAM,mBAAmB,GAAG,QAAQ,GAAG,YAAY,GAAG,WAAW,CAAA;AAYvE,gFAAgF;AAChF,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,IAAI,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;CACb;AAED;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAU3F;AAOD;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,SAAS,eAAe,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAIxH;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAG9F"}
@@ -0,0 +1,11 @@
1
+ import type { PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
2
+ import { NS } from '../locales.ts';
3
+ /** Full props: shell.overlay (root scope) gives the standard `useSessions`. */
4
+ export type SharePreviewProps = PropsRuntime<'shell.overlay'> & PropsLocale<typeof NS>;
5
+ /**
6
+ * Share-card preview panel. Fixed at the top of the overlay layer, scrollable
7
+ * (long transcripts must be inspectable), all-inline-styled like the card —
8
+ * this feature adds no page CSS by design (PLAN §9.4).
9
+ */
10
+ export declare function SharePreview({ useSessions, t }: SharePreviewProps): import("react").JSX.Element | null;
11
+ //# sourceMappingURL=share-preview.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"share-preview.d.ts","sourceRoot":"","sources":["../../../../src/client/share/share-preview.tsx"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AAEjF,OAAO,EAAE,EAAE,EAAE,MAAM,eAAe,CAAA;AASlC,+EAA+E;AAC/E,MAAM,MAAM,iBAAiB,GACzB,YAAY,CAAC,eAAe,CAAC,GAC7B,WAAW,CAAC,OAAO,EAAE,CAAC,CAAA;AAqH1B;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,EAAE,iBAAiB,sCA6HjE"}
@@ -0,0 +1,17 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots';
3
+ import { NS } from '../locales.ts';
4
+ /** Which range the user picked: everything, or the last N turns. */
5
+ export type SharePreset = 'all' | number;
6
+ /** Props: locale seat + the two callbacks the info sheet wires to its layer stack. */
7
+ export type ShareRangeSheetProps = PropsLocale<typeof NS> & {
8
+ /** True while fetch → render → rasterize runs: options and confirm lock, confirm copy flips to 生成中. */
9
+ busy: boolean;
10
+ /** Confirmed a range — the caller starts the pipeline. */
11
+ onConfirm: (preset: SharePreset) => void;
12
+ /** Close request (mask tap, ✕, or the back-driven close). */
13
+ onClose: () => void;
14
+ };
15
+ /** The share-range sheet. Rendered only while open (the caller owns the layer stack). */
16
+ export declare function ShareRangeSheet({ busy, onConfirm, onClose, t }: ShareRangeSheetProps): ReactNode;
17
+ //# sourceMappingURL=share-sheet.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"share-sheet.d.ts","sourceRoot":"","sources":["../../../../src/client/share/share-sheet.tsx"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAiB,SAAS,EAAE,MAAM,OAAO,CAAA;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AAEnE,OAAO,EAAE,EAAE,EAAE,MAAM,eAAe,CAAA;AAGlC,oEAAoE;AACpE,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,MAAM,CAAA;AAExC,sFAAsF;AACtF,MAAM,MAAM,oBAAoB,GAC5B,WAAW,CAAC,OAAO,EAAE,CAAC,GACtB;IACA,uGAAuG;IACvG,IAAI,EAAE,OAAO,CAAA;IACb,0DAA0D;IAC1D,SAAS,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAA;IACxC,6DAA6D;IAC7D,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB,CAAA;AA8FH,yFAAyF;AACzF,wBAAgB,eAAe,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE,EAAE,oBAAoB,GAAG,SAAS,CA6EhG"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Pure text → segment fold for share-card text blocks (ticket 03).
3
+ *
4
+ * Deliberately NOT a markdown parser: the transcripts the share card renders
5
+ * are *mostly* markdown, and the readability wins that matter on a phone-size
6
+ * image are structures recognizable from line shape alone — fenced/indented
7
+ * code, list items, pipe tables. Everything else stays one pre-wrapped
8
+ * paragraph, which degrades gracefully (raw text still reads), instead of
9
+ * half-parsing markup into broken pseudo-HTML.
10
+ *
11
+ * No React and no DOM in this file on purpose: the share-image check script
12
+ * (ticket 04) imports it through Node type stripping, which rejects JSX, so
13
+ * the fold must live in a JSX-free module.
14
+ */
15
+ /** One recognized paragraph shape inside a text block. */
16
+ export type ShareTextSegment = {
17
+ kind: 'para';
18
+ text: string;
19
+ } | {
20
+ kind: 'code';
21
+ lines: string[];
22
+ } | {
23
+ kind: 'list';
24
+ items: Array<{
25
+ marker: string;
26
+ text: string;
27
+ }>;
28
+ } | {
29
+ kind: 'table';
30
+ lines: string[];
31
+ };
32
+ /**
33
+ * Fold one text block's raw string into display segments, top to bottom.
34
+ *
35
+ * Rules, in the order each line is tested:
36
+ * - ```/~~~ fences switch to verbatim code until the closing fence (or the
37
+ * end of the block — an unterminated fence must not swallow nothing);
38
+ * - a 4-space/tab indent opens verbatim code; a blank line between two
39
+ * indented lines stays inside the block (lazy continuation);
40
+ * - two consecutive pipe rows open a table block, collected while rows last;
41
+ * - a `-`/`*`/`+`/`N.`/`N)` line opens a list, collected while items last;
42
+ * - blank lines end the current paragraph; anything else accumulates into the
43
+ * current paragraph (rendered pre-wrap, so manual line breaks survive).
44
+ * @param text - raw text-block content from the share-export route.
45
+ * @returns the segments in reading order; never contains empty segments.
46
+ */
47
+ export declare function segmentShareText(text: string): ShareTextSegment[];
48
+ //# sourceMappingURL=text-layout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text-layout.d.ts","sourceRoot":"","sources":["../../../../src/client/share/text-layout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,0DAA0D;AAC1D,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,GACjC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,GAChE;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,CAAA;AAqBtC;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,EAAE,CA0FjE"}
@@ -1,2 +1,2 @@
1
- export declare const HEADER_CSS = "/* ---------- session header five-piece reflow (< 768px) ---------- */\n\n/* New elements render unconditionally (React does not know about media\n queries); default them to hidden so 768px+ never sees them, then\n re-enable inside the phone block below. Belt-and-braces alongside the\n scoped rules \u2014 mirrors the existing [data-mobile-nav=\"*\"] desktop no-op\n list in misc.css.ts. */\n[data-mobile-nav=\"header-back\"],\n[data-mobile-nav=\"header-viewrow\"],\n[data-mobile-nav=\"header-activity\"],\n[data-mobile-nav=\"header-info\"],\n[data-mobile-nav=\"header-workbench\"],\n/* The workbench close pill's default-hidden used to live in compat.css.ts \u2014\n but EVERYTHING in layout/compat/misc sits inside one shared\n (max-width: 1023px) block (opened at the top of layout, closed at the end\n of misc), so that \"top-level\" rule silently never applied at >=1024 and\n the pill rendered as an unstyled button on desktop (2026-08-17 report).\n This file is appended after the shared block closes, so here it is truly\n global; the <=767px :has() show rule in compat overrides it with\n !important. */\n[data-mobile-nav=\"better-sidebar-close\"] {\n display: none;\n}\n\n@media (max-width: 767px) {\n /* The Files action targeted the dsh-web-ui-all (aionui) explorer sheet \u2014\n a different, unrelated suite from the workbench entry this slice adds\n (dsh-better-sidebar). Hidden here only; 768-1023px keeps it exactly as\n v1.0.0 shipped it. */\n [data-mobile-nav=\"files\"] {\n display: none !important;\n }\n\n /* --- three-column header: [back 92px] [title, truly centered] [utilities 92px] --- */\n /* The bottom padding is the view-switch row's seat: that row is\n position:absolute (see the bottom of this file \u2014 it renders two levels\n deep inside headerActions and can never be a grid item of titleRow), so\n without a reserve it would hang over the message list. 28px = its\n height. Unconditional rather than header:has([data-mobile-nav=\n \"header-viewrow\"]) on purpose: :has() is silently dropped by pre-105\n WebViews (see AGENTS.md) and a 28px overlap on the first message is a\n worse failure than 28px of empty band in the (never observed in\n practice) single-view session. Total header chrome: 48 + 28 = 76px.\n Left/right 8px (S2.1 report leftover, fixed in S4): the official header\n had zero horizontal padding, so the utilities button cluster's right\n edge sat flush against the screen edge (x=390 at 390px width, no\n margin). The 92px grid columns absorb this fine \u2014 they're fixed track\n widths, only the center 1fr column shrinks by 16px. */\n [data-phase] header {\n position: relative;\n /* Above the scroller's plain and relative-positioned message content, so\n the ::after fade strip below actually paints over the messages sliding\n under it (positioned z-auto boxes otherwise win by DOM order \u2014 the\n scroller comes after the header). Deliberately tiny: the seat (7),\n scroll-to-bottom (8) and every overlay this plugin owns (30+) stay\n above. */\n z-index: 2;\n padding: 0 8px 28px !important;\n }\n /* No header bottom line \u2014 fade instead (real-device round 2, 2026-08-17).\n The official header draws its border as a `::after` 1px bar (the\n `border-bottom` on the header itself is transparent, just a layout\n reserve \u2014 dsh-client-ui-conversation lib/client.js \".wSkVaW_header{\n border-bottom:1px solid #0000}.wSkVaW_header:after{...height:1px;\n position:absolute;bottom:1px...}\", verified live 2026-08-17), so the\n bar is repurposed rather than hidden: restyled into a 20px strip\n hanging just below the header that fades the messages in \u2014 the same\n look the scroller's mask-image used to produce, without a mask on a\n scroll container (iOS WebKit renders that as fog over the header, see\n composer.css.ts \"no divider above OR below the message list\",\n 2026-08-23). color-mix instead of the transparent keyword: the host\n writes its own seat gradient that way, and plain `transparent` is\n rgba(0,0,0,0) \u2014 WebKit interpolates it through grey. */\n [data-phase] header::after {\n height: 20px !important;\n top: 100% !important;\n bottom: auto !important;\n background: linear-gradient(to bottom, var(--dsw-alias-bg-base), color-mix(in srgb, var(--dsw-alias-bg-base) 0%, transparent)) !important;\n }\n /* grid-row: 1 on every item is load-bearing, not decoration (S2.1 fix for\n the \"\u6807\u9898\u88AB\u6324\u4E0B\u53BB\" report). The three items are placed with explicit\n grid-column but arrive in DOM order crumbs(2) \u2192 headerActions(1) \u2192\n headerUtilities(3): sparse auto-placement never moves its cursor\n backwards, so headerActions and headerUtilities were pushed onto an\n implicit SECOND row. Measured at 390px: row1 = crumbs 28px, row2 =\n back button 44px, titleRow = 72px with the title stuck at the top\n instead of centered. Pinning the row makes it one 48px band again. */\n [data-phase] header [class$=\"_titleRow\"] {\n position: relative;\n display: grid;\n grid-template-columns: 92px 1fr 92px;\n grid-template-rows: minmax(48px, auto);\n align-items: center;\n min-height: 48px;\n padding: 0 !important;\n }\n /* Flatten the official cluster so its two children (crumbs, headerActions)\n become direct grid items of titleRow instead of a nested flex box. */\n [data-phase] header [class$=\"_titleCluster\"] {\n display: contents;\n }\n /* margin-left: 0 cancels layout.css.ts's shared <=1023px\n \"margin-left: auto !important\" on this exact selector (it pushed the\n Files button to the row's right edge in the old non-grid header;\n inside our grid column it instead shoves the back button 48px right of\n the column's start \u2014 the auto margin absorbs the leftover space\n between the button's content width and the 92px column). */\n [data-phase] header [class$=\"_headerActions\"] {\n grid-column: 1;\n grid-row: 1;\n justify-self: start;\n margin-left: 0 !important;\n display: flex;\n align-items: center;\n min-width: 0;\n }\n /* layout.css.ts's shared <=1023px block hides this element outright\n (header > :first-child > :last-child { display: none !important },\n specificity (0,3,1) \u2014 v1.0.0 relocated the desktop Session-log capsule\n to the drawer footer on every narrow width). That selector is repeated\n here at equal specificity so the later source order wins the display\n property; the class-suffix selector alone (0,2,1) would silently lose. */\n [data-phase] header [class$=\"_headerUtilities\"],\n [data-phase] header > :first-child > :last-child {\n grid-column: 3;\n grid-row: 1;\n justify-self: end;\n display: flex !important;\n align-items: center;\n gap: 4px;\n min-width: 0;\n }\n /* justify-self: stretch (the grid default) so this fills the whole 1fr\n column as a definite-width box \u2014 center/center-content happens inside\n it. justify-self: center (tried first) sizes the item to its content\n with no automatic column cap, and the obvious fix \u2014 max-width: 100% \u2014\n resolves against the WRONG box here (measured 93.6px at 390px, where\n the column is ~206px): the item's DOM parent (titleCluster) is\n display:contents, and percentage max-width does not reliably resolve\n through a boxless ancestor to the grid track. Stretching sidesteps the\n percentage entirely \u2014 but layout.css.ts's shared <=1023px block ALSO\n caps this exact selector at max-width: 24vw !important (93.6px at\n 390px \u2014 the value this rule used to silently inherit once its own\n max-width was dropped in favor of stretch), so that must be reset to\n none here too. */\n [data-phase] header [class$=\"_crumbs\"] {\n grid-column: 2;\n grid-row: 1;\n justify-self: stretch;\n max-width: none !important;\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 0;\n overflow: hidden;\n }\n /* Subagent sessions keep ONE ancestor crumb (2026-08-18): the PC header\n lets you tap the parent session's title to switch back \u2014 that affordance\n was lost on phone when the whole parent chain was hidden as \"\u591A\u4F59\u9879\".\n Show only the immediate parent (for agent-teams member sessions that IS\n the captain/main session; deeper ancestors stay hidden \u2014 a 3-level chain\n cannot fit the ~206px center column at 390px). The parent crumb is the\n official breadcrumb <button> (dsh-client-ui-conversation renders\n open(parentId) as its onClick), so tapping needs no JS from this plugin,\n and main sessions (chain length 1) have no :nth-last-child(2) seg at\n all \u2014 the centered single title is pixel-identical to before. */\n [data-phase] header [class$=\"_crumbSeg\"]:not(:nth-last-child(-n+2)) {\n display: none !important;\n }\n [data-phase] header [class$=\"_crumbSeg\"]:nth-last-child(2) {\n display: inline-flex;\n align-items: center;\n flex: none;\n min-width: 0;\n max-width: 38%;\n }\n /* Ancestors BEFORE the parent seg are hidden, which would leave the\n parent seg's own leading \"/\" dangling at the start of the title. */\n [data-phase] header [class$=\"_crumbSeg\"]:nth-last-child(2) [class$=\"_crumbSep\"] {\n display: none !important;\n }\n /* Parent crumb: small tertiary text so the 19px current title stays the\n visual anchor; min-height keeps a finger-sized target inside the 48px\n row. Specificity (0,4,1) outranks the shared 19px _crumb rule below. */\n [data-phase] header [class$=\"_crumbSeg\"]:nth-last-child(2) [class$=\"_crumb\"] {\n font-size: 13px;\n font-weight: 400;\n color: var(--dsw-alias-label-tertiary, rgba(0, 0, 0, .45));\n min-height: 44px;\n display: inline-flex;\n align-items: center;\n padding: 0 2px 0 6px;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n [data-phase] header [class$=\"_crumbSeg\"]:last-child {\n display: flex;\n align-items: center;\n min-width: 0;\n max-width: 100%;\n }\n /* The last seg's own \"/\" is the divider between parent and current title \u2014\n it only exists when there IS an ancestor (index > 0), so it can simply\n stay visible now that the parent seg shows. (It used to be hidden here:\n with the parent hidden it dangled before the title.) */\n /* BOTH selectors are required: [class$=] matches the whole class\n ATTRIBUTE's suffix, and the current-session title button's attribute is\n \"wSkVaW_crumb wSkVaW_crumbCurrent\" \u2014 it ends in _crumbCurrent, so the _crumb\n suffix selector alone silently missed the one element that matters\n (measured live: the title stayed 14px while the rule sat in the bundle,\n 2026-08-17). */\n [data-phase] header [class$=\"_crumb\"],\n [data-phase] header [class$=\"_crumbCurrent\"] {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n min-width: 0;\n max-width: 100%;\n /* Real-device feedback (2026-08-17): the session title read too small\n next to the enlarged header icons \u2014 19px sits just under the home\n screen's 22px workspace title, keeping the two-level hierarchy. */\n font-size: 19px;\n font-weight: 600;\n }\n\n /* Running-status dot: no official element to reposition (the header\n renders only the crumb title, the two action slots, and the tablist \u2014\n no status indicator), so this is the plan's documented fallback \u2014\n read the data ourselves and draw it (effects/header-status.ts stamps\n data-mobile-nav-dot on the frame). A ::after on the title crumb keeps\n it truly inline with the (centered, truncated) text instead of a\n separately positioned box that would fight the centering math. */\n [data-phase] header [class$=\"_crumbCurrent\"]::after {\n content: '';\n display: none;\n width: 6px;\n height: 6px;\n margin-left: 6px;\n border-radius: 50%;\n vertical-align: middle;\n }\n [data-mobile-nav=\"frame\"][data-mobile-nav-dot] header [class$=\"_crumbCurrent\"]::after {\n display: inline-block;\n }\n [data-mobile-nav=\"frame\"][data-mobile-nav-dot=\"ongoing\"] header [class$=\"_crumbCurrent\"]::after {\n background: var(--dsw-alias-state-business-primary, #4f6ef7);\n }\n [data-mobile-nav=\"frame\"][data-mobile-nav-dot=\"warning\"] header [class$=\"_crumbCurrent\"]::after {\n background: var(--dsw-alias-state-warn-primary, #d97706);\n }\n [data-mobile-nav=\"frame\"][data-mobile-nav-dot=\"done\"] header [class$=\"_crumbCurrent\"]::after {\n background: var(--dsw-alias-state-success-primary, #16a34a);\n }\n\n /* Everything else the official header puts in these two list slots \u2014 the\n agent-preset mode badge, the jobs/subagent trigger, the desktop\n Session-log download capsule, and this plugin's own legacy directory\n toggle \u2014 is the \"\u4E0D\u5E38\u9A7B\" set the design defers to the S4 info card.\n Scoped by the slots' own [data-slot] wrapper (a stable, non-hashed\n contract marker) rather than enumerating each registrant, so a future\n third-party header action is hidden by default too. */\n [data-phase] header [data-slot=\"conversation.session.header.actions\"] > *,\n [data-phase] header [data-slot=\"conversation.session.header.utilities\"] > * {\n display: none !important;\n }\n /* Native background-task / subagent entries (rewritten for DSH 0.1.1,\n 2026-08-21). Three upstream changes killed the old \"park the root as a\n zero-width anchor, hide the trigger, forward the tap\" design at once:\n\n - the subagent entry moved out of header.actions into the new\n conversation.session.header.lineage slot (inside the breadcrumb);\n - its root's class attribute is now \"ZKlsPq_root \" \u2014 with a TRAILING\n SPACE \u2014 so a [class$=\"_root\"] anchor matches nothing at all;\n - the trigger only reacts to TRUSTED input: .click() and synthetic\n pointer/mouse events leave aria-expanded untouched (measured on device\n with every plugin override stripped), so forwarding a tap in JS can\n never open it again.\n\n The trigger's geometry is therefore owned at runtime by\n effects/native-trigger-overlay.ts, which lays the REAL button\n transparently over the activity pill \u2014 a genuine finger tap lands on the\n official element. Nothing in this stylesheet may size, hide, or set\n pointer-events on the trigger: that is exactly what would take the tap\n target away again.\n\n CSS keeps only two chores. Dissolve the root so its box (and its\n stray separator, hidden below) stops occupying the breadcrumb, and let\n the trigger participate from wherever the overlay pins it. And no menu\n re-anchoring anymore: the 0.1.1 popover portals to <body> as\n position:fixed and positions itself from the trigger's viewport rect\n (measured 336px wide, fully on screen at 411px). */\n [data-phase] header [data-slot=\"conversation.session.header.actions\"] > *:has(> button[class$=\"_trigger\"]),\n [data-phase] header [data-slot=\"conversation.session.header.lineage\"] > *:has(> button[class$=\"_trigger\"]) {\n display: contents !important;\n }\n [data-phase] header [data-slot=\"conversation.session.header.actions\"] [class$=\"_separator\"],\n [data-phase] header [data-slot=\"conversation.session.header.lineage\"] [class$=\"_separator\"] {\n display: none !important;\n }\n /* The agent-preset mode badge specifically survives the blanket hide\n above: layout.css.ts's shared <=1023px block targets it directly\n (header [class$=\"_label\"]:has(> svg) { display: block !important }),\n specificity (0,3,1), higher than the [data-slot] wrapper rule (0,2,1) \u2014\n display wins on specificity before source order, so a same-selector\n override is needed here too. */\n [data-phase] header [class$=\"_label\"]:has(> svg) {\n display: none !important;\n }\n [data-phase] header [data-slot=\"conversation.session.header.actions\"] > [data-mobile-nav=\"header-back\"] {\n display: inline-flex !important;\n }\n /* header-viewrow is position:absolute (see below), so its re-shown\n display value must match the flex layout its own rule declares. */\n [data-phase] header [data-slot=\"conversation.session.header.actions\"] > [data-mobile-nav=\"header-viewrow\"] {\n display: flex !important;\n }\n /* Our own chip lives in the same slot, so the blanket hide above swallows\n it unless it is named here too (it was, on the first build: the chip\n rendered with the right counts and painted nothing). */\n [data-phase] header [data-slot=\"conversation.session.header.actions\"] > [data-mobile-nav=\"header-activity\"] {\n display: inline-flex !important;\n }\n [data-phase] header [data-slot=\"conversation.session.header.utilities\"] > [data-mobile-nav=\"header-info\"],\n [data-phase] header [data-slot=\"conversation.session.header.utilities\"] > [data-mobile-nav=\"header-workbench\"] {\n display: inline-flex !important;\n }\n\n /* Back button: 44pt touch target, leftmost column. */\n [data-mobile-nav=\"header-back\"] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 44px;\n height: 44px;\n padding: 0;\n border: none;\n background: transparent;\n color: var(--dsw-alias-label-primary, inherit);\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n [data-mobile-nav=\"header-back\"]:active {\n opacity: .6;\n }\n\n /* Info + workbench utility buttons, rightmost column. */\n [data-mobile-nav=\"header-info\"],\n [data-mobile-nav=\"header-workbench\"] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 34px;\n height: 34px;\n padding: 0;\n border: none;\n border-radius: 50%;\n background: transparent;\n color: var(--dsw-alias-label-secondary, inherit);\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n [data-mobile-nav=\"header-info\"]:active,\n [data-mobile-nav=\"header-workbench\"]:active {\n background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));\n }\n /* Icon family unification (real-device round 2, 2026-08-17): the \u24D8 text\n glyph is gone (MobileSessionHeader.tsx now renders IconInfoOutline16,\n a local 16px SVG built to match the primitives icon family) and the\n workbench button's IconPanelLeftOutline16 is mirrored into a\n panel-RIGHT glyph \u2014 there's no IconPanelRightOutline16 in primitives\n (checked lib/types/icons/index.d.ts), and the plugin's own right-side\n panel semantics are exactly the left icon flipped. Both buttons now\n carry a same-size (16px), same-stroke-weight icon. */\n [data-mobile-nav=\"header-workbench\"] svg {\n transform: scaleX(-1);\n }\n\n /* Official Chat/Trajectory tablist: removed from layout entirely (S2.1 \u2014\n visibility:hidden still held a 27px row, so the header carried the\n view-switch row's band TWICE: 72 + 27 + gap = 104px measured at 390px,\n and a real iPhone added ~54px of notch on top of that). The header's own\n padding-bottom above is now the row's seat.\n display:none does NOT break the view switch: HTMLElement.click()\n dispatches synthetically and fires React's handler regardless of\n rendering \u2014 only real pointer hit-testing needs a box, and this plugin\n never relies on it (MobileSessionHeader always calls .click()). */\n [data-phase] header [class$=\"_tabs\"][role=\"tablist\"] {\n display: none;\n }\n\n /* View-switch row: \"current view name + dots\", replacing the (still\n present, still clickable) official tablist visually. It renders inside\n headerActions/headerUtilities, but titleRow's own position:relative\n (set above) makes IT the containing block for absolute descendants \u2014\n display:contents on titleCluster does not break that search \u2014 so\n top:100% sits directly under the title regardless of the title row's\n actual height, with no hardcoded offset to keep in sync. */\n /* Activity chip (subagents / background tasks) rides the right end of the\n same band. It is a SIBLING of the switch button, not a child \u2014 that\n button spans the full row, so nesting would make every tap on the chip\n switch views too \u2014 and it shares titleRow as its containing block, so\n top:100% lands both on the same 28px line. z-index puts it above the\n row it overlaps. */\n [data-mobile-nav=\"header-activity\"] {\n position: absolute;\n right: 0;\n top: 100%;\n z-index: 1;\n display: inline-flex;\n align-items: center;\n gap: 9px;\n height: 28px;\n padding: 0 2px 0 10px;\n border: none;\n background: transparent;\n color: var(--dsw-alias-label-secondary, rgba(0, 0, 0, .5));\n font-family: inherit;\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n /* pointer-events:none so the invisible official trigger laid over this pill\n (effects/native-trigger-overlay.ts) is what a tap reaches. The pill is\n visuals only. */\n [data-mobile-nav=\"activity-pill\"] {\n pointer-events: none;\n display: inline-flex;\n align-items: center;\n gap: 3px;\n height: 28px;\n padding: 0;\n border: none;\n background: transparent;\n color: inherit;\n font-family: inherit;\n font-size: 12px;\n line-height: 18px;\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n [data-mobile-nav=\"activity-pill\"]:active {\n opacity: .55;\n }\n [data-mobile-nav=\"activity-count\"] {\n font-variant-numeric: tabular-nums;\n }\n /* Steady green = everything settled. */\n [data-mobile-nav=\"activity-dot\"] {\n display: block;\n width: 6px;\n height: 6px;\n margin-left: 1px;\n border-radius: 50%;\n background: var(--dsw-alias-state-success-primary, #16a34a);\n }\n /* A job that failed or was killed is not a success \u2014 never paint it green. */\n [data-activity-state=\"warning\"] [data-mobile-nav=\"activity-dot\"] {\n background: var(--dsw-alias-state-warn-primary, #d97706);\n }\n /* Pulsing blue = still running. Same state palette as the session dot. */\n [data-activity-state=\"running\"] [data-mobile-nav=\"activity-dot\"] {\n background: var(--dsw-alias-state-business-primary, #4f6ef7);\n animation: dsh-zen-activity-pulse 1.4s ease-in-out infinite;\n }\n @keyframes dsh-zen-activity-pulse {\n 0%, 100% { opacity: 1; transform: scale(1); }\n 50% { opacity: .45; transform: scale(.72); }\n }\n @media (prefers-reduced-motion: reduce) {\n [data-activity-state=\"running\"] [data-mobile-nav=\"activity-dot\"] {\n animation: none;\n }\n }\n\n [data-mobile-nav=\"header-viewrow\"] {\n position: absolute;\n left: 0;\n right: 0;\n top: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n height: 28px;\n padding: 0;\n border: none;\n background: transparent;\n color: var(--dsw-alias-label-secondary, rgba(0, 0, 0, .5));\n font-family: inherit;\n font-size: 13px;\n line-height: 18px;\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n [data-mobile-nav=\"header-viewrow-dots\"] {\n display: inline-flex;\n align-items: center;\n gap: 3px;\n }\n [data-mobile-nav=\"header-viewrow-dots\"] > i {\n display: block;\n width: 4px;\n height: 4px;\n border-radius: 50%;\n background: currentColor;\n opacity: .35;\n font-style: normal;\n }\n [data-mobile-nav=\"header-viewrow-dots\"] > i[data-active] {\n opacity: 1;\n }\n}\n";
1
+ export declare const HEADER_CSS = "/* ---------- session header five-piece reflow (< 768px) ---------- */\n\n/* New elements render unconditionally (React does not know about media\n queries); default them to hidden so 768px+ never sees them, then\n re-enable inside the phone block below. Belt-and-braces alongside the\n scoped rules \u2014 mirrors the existing [data-mobile-nav=\"*\"] desktop no-op\n list in misc.css.ts. */\n[data-mobile-nav=\"header-back\"],\n[data-mobile-nav=\"header-viewrow\"],\n[data-mobile-nav=\"header-activity\"],\n[data-mobile-nav=\"header-info\"],\n[data-mobile-nav=\"header-workbench\"],\n/* The workbench close pill's default-hidden used to live in compat.css.ts \u2014\n but EVERYTHING in layout/compat/misc sits inside one shared\n (max-width: 1023px) block (opened at the top of layout, closed at the end\n of misc), so that \"top-level\" rule silently never applied at >=1024 and\n the pill rendered as an unstyled button on desktop (2026-08-17 report).\n This file is appended after the shared block closes, so here it is truly\n global; the <=767px :has() show rule in compat overrides it with\n !important. */\n[data-mobile-nav=\"better-sidebar-close\"] {\n display: none;\n}\n\n@media (max-width: 767px) {\n /* The Files action targeted the dsh-web-ui-all (aionui) explorer sheet \u2014\n a different, unrelated suite from the workbench entry this slice adds\n (dsh-better-sidebar). Hidden here only; 768-1023px keeps it exactly as\n v1.0.0 shipped it. */\n [data-mobile-nav=\"files\"] {\n display: none !important;\n }\n\n /* --- three-column header: [back 92px] [title, truly centered] [utilities 92px] --- */\n /* The bottom padding is the view-switch row's seat: that row is\n position:absolute (see the bottom of this file \u2014 it renders two levels\n deep inside headerActions and can never be a grid item of titleRow), so\n without a reserve it would hang over the message list. 28px = its\n height. Unconditional rather than header:has([data-mobile-nav=\n \"header-viewrow\"]) on purpose: :has() is silently dropped by pre-105\n WebViews (see AGENTS.md) and a 28px overlap on the first message is a\n worse failure than 28px of empty band in the (never observed in\n practice) single-view session. Total header chrome: 48 + 28 = 76px.\n Left/right 8px (S2.1 report leftover, fixed in S4): the official header\n had zero horizontal padding, so the utilities button cluster's right\n edge sat flush against the screen edge (x=390 at 390px width, no\n margin). The 92px grid columns absorb this fine \u2014 they're fixed track\n widths, only the center 1fr column shrinks by 16px. */\n [data-phase] header {\n position: relative;\n /* Above the scroller's plain and relative-positioned message content, so\n the ::after fade strip below actually paints over the messages sliding\n under it (positioned z-auto boxes otherwise win by DOM order \u2014 the\n scroller comes after the header). Deliberately tiny: the seat (7),\n scroll-to-bottom (8) and every overlay this plugin owns (30+) stay\n above. */\n z-index: 2;\n padding: 0 8px 28px !important;\n }\n /* No header bottom line \u2014 fade instead (real-device round 2, 2026-08-17).\n The official header draws its border as a `::after` 1px bar (the\n `border-bottom` on the header itself is transparent, just a layout\n reserve \u2014 dsh-client-ui-conversation lib/client.js \".wSkVaW_header{\n border-bottom:1px solid #0000}.wSkVaW_header:after{...height:1px;\n position:absolute;bottom:1px...}\", verified live 2026-08-17), so the\n bar is repurposed rather than hidden: restyled into a 20px strip\n hanging just below the header that fades the messages in \u2014 the same\n look the scroller's mask-image used to produce, without a mask on a\n scroll container (iOS WebKit renders that as fog over the header, see\n composer.css.ts \"no divider above OR below the message list\",\n 2026-08-23). color-mix instead of the transparent keyword: the host\n writes its own seat gradient that way, and plain `transparent` is\n rgba(0,0,0,0) \u2014 WebKit interpolates it through grey. */\n [data-phase] header::after {\n height: 20px !important;\n top: 100% !important;\n bottom: auto !important;\n background: linear-gradient(to bottom, var(--dsw-alias-bg-base), color-mix(in srgb, var(--dsw-alias-bg-base) 0%, transparent)) !important;\n }\n /* grid-row: 1 on every item is load-bearing, not decoration (S2.1 fix for\n the \"\u6807\u9898\u88AB\u6324\u4E0B\u53BB\" report). The three items are placed with explicit\n grid-column but arrive in DOM order crumbs(2) \u2192 headerActions(1) \u2192\n headerUtilities(3): sparse auto-placement never moves its cursor\n backwards, so headerActions and headerUtilities were pushed onto an\n implicit SECOND row. Measured at 390px: row1 = crumbs 28px, row2 =\n back button 44px, titleRow = 72px with the title stuck at the top\n instead of centered. Pinning the row makes it one 48px band again. */\n [data-phase] header [class$=\"_titleRow\"] {\n position: relative;\n display: grid;\n grid-template-columns: 92px 1fr 92px;\n grid-template-rows: minmax(48px, auto);\n align-items: center;\n min-height: 48px;\n padding: 0 !important;\n }\n /* Flatten the official cluster so its two children (crumbs, headerActions)\n become direct grid items of titleRow instead of a nested flex box. */\n [data-phase] header [class$=\"_titleCluster\"] {\n display: contents;\n }\n /* margin-left: 0 cancels layout.css.ts's shared <=1023px\n \"margin-left: auto !important\" on this exact selector (it pushed the\n Files button to the row's right edge in the old non-grid header;\n inside our grid column it instead shoves the back button 48px right of\n the column's start \u2014 the auto margin absorbs the leftover space\n between the button's content width and the 92px column). */\n [data-phase] header [class$=\"_headerActions\"] {\n grid-column: 1;\n grid-row: 1;\n justify-self: start;\n margin-left: 0 !important;\n display: flex;\n align-items: center;\n min-width: 0;\n }\n /* layout.css.ts's shared <=1023px block hides this element outright\n (header > :first-child > :last-child { display: none !important },\n specificity (0,3,1) \u2014 v1.0.0 relocated the desktop Session-log capsule\n to the drawer footer on every narrow width). That selector is repeated\n here at equal specificity so the later source order wins the display\n property; the class-suffix selector alone (0,2,1) would silently lose. */\n [data-phase] header [class$=\"_headerUtilities\"],\n [data-phase] header > :first-child > :last-child {\n grid-column: 3;\n grid-row: 1;\n justify-self: end;\n display: flex !important;\n align-items: center;\n gap: 4px;\n min-width: 0;\n }\n /* justify-self: stretch (the grid default) so this fills the whole 1fr\n column as a definite-width box \u2014 center/center-content happens inside\n it. justify-self: center (tried first) sizes the item to its content\n with no automatic column cap, and the obvious fix \u2014 max-width: 100% \u2014\n resolves against the WRONG box here (measured 93.6px at 390px, where\n the column is ~206px): the item's DOM parent (titleCluster) is\n display:contents, and percentage max-width does not reliably resolve\n through a boxless ancestor to the grid track. Stretching sidesteps the\n percentage entirely \u2014 but layout.css.ts's shared <=1023px block ALSO\n caps this exact selector at max-width: 24vw !important (93.6px at\n 390px \u2014 the value this rule used to silently inherit once its own\n max-width was dropped in favor of stretch), so that must be reset to\n none here too. */\n [data-phase] header [class$=\"_crumbs\"] {\n grid-column: 2;\n grid-row: 1;\n justify-self: stretch;\n max-width: none !important;\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 0;\n overflow: hidden;\n }\n /* Subagent sessions keep ONE ancestor crumb (2026-08-18): the PC header\n lets you tap the parent session's title to switch back \u2014 that affordance\n was lost on phone when the whole parent chain was hidden as \"\u591A\u4F59\u9879\".\n Show only the immediate parent (for agent-teams member sessions that IS\n the captain/main session; deeper ancestors stay hidden \u2014 a 3-level chain\n cannot fit the ~206px center column at 390px). The parent crumb is the\n official breadcrumb <button> (dsh-client-ui-conversation renders\n open(parentId) as its onClick), so tapping needs no JS from this plugin,\n and main sessions (chain length 1) have no :nth-last-child(2) seg at\n all \u2014 the centered single title is pixel-identical to before. */\n [data-phase] header [class$=\"_crumbSeg\"]:not(:nth-last-child(-n+2)) {\n display: none !important;\n }\n [data-phase] header [class$=\"_crumbSeg\"]:nth-last-child(2) {\n display: inline-flex;\n align-items: center;\n flex: none;\n min-width: 0;\n max-width: 38%;\n }\n /* Ancestors BEFORE the parent seg are hidden, which would leave the\n parent seg's own leading \"/\" dangling at the start of the title. */\n [data-phase] header [class$=\"_crumbSeg\"]:nth-last-child(2) [class$=\"_crumbSep\"] {\n display: none !important;\n }\n /* Parent crumb: small tertiary text so the 19px current title stays the\n visual anchor; min-height keeps a finger-sized target inside the 48px\n row. Specificity (0,4,1) outranks the shared 19px _crumb rule below. */\n [data-phase] header [class$=\"_crumbSeg\"]:nth-last-child(2) [class$=\"_crumb\"] {\n font-size: 13px;\n font-weight: 400;\n color: var(--dsw-alias-label-tertiary, rgba(0, 0, 0, .45));\n min-height: 44px;\n display: inline-flex;\n align-items: center;\n padding: 0 2px 0 6px;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n [data-phase] header [class$=\"_crumbSeg\"]:last-child {\n display: flex;\n align-items: center;\n min-width: 0;\n max-width: 100%;\n }\n /* The last seg's own \"/\" is the divider between parent and current title \u2014\n it only exists when there IS an ancestor (index > 0), so it can simply\n stay visible now that the parent seg shows. (It used to be hidden here:\n with the parent hidden it dangled before the title.) */\n /* BOTH selectors are required: [class$=] matches the whole class\n ATTRIBUTE's suffix, and the current-session title button's attribute is\n \"wSkVaW_crumb wSkVaW_crumbCurrent\" \u2014 it ends in _crumbCurrent, so the _crumb\n suffix selector alone silently missed the one element that matters\n (measured live: the title stayed 14px while the rule sat in the bundle,\n 2026-08-17). */\n [data-phase] header [class$=\"_crumb\"],\n [data-phase] header [class$=\"_crumbCurrent\"] {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n min-width: 0;\n max-width: 100%;\n /* Real-device feedback (2026-08-17): the session title read too small\n next to the enlarged header icons \u2014 19px sits just under the home\n screen's 22px workspace title, keeping the two-level hierarchy. */\n font-size: 19px;\n font-weight: 600;\n }\n\n /* Running-status dot: no official element to reposition (the header\n renders only the crumb title, the two action slots, and the tablist \u2014\n no status indicator), so this is the plan's documented fallback \u2014\n read the data ourselves and draw it (effects/header-status.ts stamps\n data-mobile-nav-dot on the frame). A ::after on the title crumb keeps\n it truly inline with the (centered, truncated) text instead of a\n separately positioned box that would fight the centering math. */\n [data-phase] header [class$=\"_crumbCurrent\"]::after {\n content: '';\n display: none;\n width: 6px;\n height: 6px;\n margin-left: 6px;\n border-radius: 50%;\n vertical-align: middle;\n }\n [data-mobile-nav=\"frame\"][data-mobile-nav-dot] header [class$=\"_crumbCurrent\"]::after {\n display: inline-block;\n }\n [data-mobile-nav=\"frame\"][data-mobile-nav-dot=\"ongoing\"] header [class$=\"_crumbCurrent\"]::after {\n background: var(--dsw-alias-state-business-primary, #4f6ef7);\n }\n [data-mobile-nav=\"frame\"][data-mobile-nav-dot=\"warning\"] header [class$=\"_crumbCurrent\"]::after {\n background: var(--dsw-alias-state-warn-primary, #d97706);\n }\n [data-mobile-nav=\"frame\"][data-mobile-nav-dot=\"done\"] header [class$=\"_crumbCurrent\"]::after {\n background: var(--dsw-alias-state-success-primary, #16a34a);\n }\n\n /* Everything else the official header puts in these two list slots \u2014 the\n agent-preset mode badge, the jobs/subagent trigger, the desktop\n Session-log download capsule, and this plugin's own legacy directory\n toggle \u2014 is the \"\u4E0D\u5E38\u9A7B\" set the design defers to the S4 info card.\n Scoped by the slots' own [data-slot] wrapper (a stable, non-hashed\n contract marker) rather than enumerating each registrant, so a future\n third-party header action is hidden by default too. */\n [data-phase] header [data-slot=\"conversation.session.header.actions\"] > *,\n [data-phase] header [data-slot=\"conversation.session.header.utilities\"] > * {\n display: none !important;\n }\n /* DSH 0.1.5's third titleRow child: the right sidebar's ExpandButton\n (dsh-client-ui-sidebar-right, slot conversation.session.header.corner\n inside div.headerCorner). Left alone it auto-placed into an implicit\n 4th grid column at the screen's top-right corner \u2014 a second panel-left\n icon next to our Workbench button, reading as \"the hidden official\n sidebar toggle is back\" (reported 2026-09-11). The button itself is the\n one our workbench control forwards to when better-sidebar is absent\n (MobileSessionHeader's toggleSidebarTarget), and a display:none button\n still receives synthetic .click() (the tablist precedent), so hiding it\n costs nothing.\n Anchors, in the specificity order they NEED (measured live, 2026-09-11):\n the corner div must be beaten with (0,3,1) \u2014 the utilities un-hide rule\n above spells header > :first-child > :last-child at exactly that\n weight, and with 0.1.5's third titleRow child the corner IS that\n :last-child now, so its flex !important outranks any lighter hide (a\n single-attribute hide loses at (0,2,1) \u2014 both spellings were tried\n live). Chaining BOTH of the corner's own anchors on one selector \u2014 the\n host's data-conversation-header-corner contract attribute AND the\n class suffix \u2014 reaches the tie, and the later source order here wins\n it. The slot wrapper rule below needs no help: nothing !important\n fights it, so its (0,2,1) settles the button. */\n [data-phase] header [data-conversation-header-corner][class$=\"_headerCorner\"],\n [data-phase] header [data-slot=\"conversation.session.header.corner\"] {\n display: none !important;\n }\n /* Native background-task / subagent entries (rewritten for DSH 0.1.1,\n 2026-08-21). Three upstream changes killed the old \"park the root as a\n zero-width anchor, hide the trigger, forward the tap\" design at once:\n\n - the subagent entry moved out of header.actions into the new\n conversation.session.header.lineage slot (inside the breadcrumb);\n - its root's class attribute is now \"ZKlsPq_root \" \u2014 with a TRAILING\n SPACE \u2014 so a [class$=\"_root\"] anchor matches nothing at all;\n - the trigger only reacts to TRUSTED input: .click() and synthetic\n pointer/mouse events leave aria-expanded untouched (measured on device\n with every plugin override stripped), so forwarding a tap in JS can\n never open it again.\n\n The trigger's geometry is therefore owned at runtime by\n effects/native-trigger-overlay.ts, which lays the REAL button\n transparently over the activity pill \u2014 a genuine finger tap lands on the\n official element. Nothing in this stylesheet may size, hide, or set\n pointer-events on the trigger: that is exactly what would take the tap\n target away again.\n\n CSS keeps only two chores. Dissolve the root so its box (and its\n stray separator, hidden below) stops occupying the breadcrumb, and let\n the trigger participate from wherever the overlay pins it. And no menu\n re-anchoring anymore: the 0.1.1 popover portals to <body> as\n position:fixed and positions itself from the trigger's viewport rect\n (measured 336px wide, fully on screen at 411px). */\n [data-phase] header [data-slot=\"conversation.session.header.actions\"] > *:has(> button[class$=\"_trigger\"]),\n [data-phase] header [data-slot=\"conversation.session.header.lineage\"] > *:has(> button[class$=\"_trigger\"]) {\n display: contents !important;\n }\n [data-phase] header [data-slot=\"conversation.session.header.actions\"] [class$=\"_separator\"],\n [data-phase] header [data-slot=\"conversation.session.header.lineage\"] [class$=\"_separator\"] {\n display: none !important;\n }\n /* The agent-preset mode badge specifically survives the blanket hide\n above: layout.css.ts's shared <=1023px block targets it directly\n (header [class$=\"_label\"]:has(> svg) { display: block !important }),\n specificity (0,3,1), higher than the [data-slot] wrapper rule (0,2,1) \u2014\n display wins on specificity before source order, so a same-selector\n override is needed here too. */\n [data-phase] header [class$=\"_label\"]:has(> svg) {\n display: none !important;\n }\n [data-phase] header [data-slot=\"conversation.session.header.actions\"] > [data-mobile-nav=\"header-back\"] {\n display: inline-flex !important;\n }\n /* header-viewrow is position:absolute (see below), so its re-shown\n display value must match the flex layout its own rule declares. */\n [data-phase] header [data-slot=\"conversation.session.header.actions\"] > [data-mobile-nav=\"header-viewrow\"] {\n display: flex !important;\n }\n /* Our own chip lives in the same slot, so the blanket hide above swallows\n it unless it is named here too (it was, on the first build: the chip\n rendered with the right counts and painted nothing). */\n [data-phase] header [data-slot=\"conversation.session.header.actions\"] > [data-mobile-nav=\"header-activity\"] {\n display: inline-flex !important;\n }\n [data-phase] header [data-slot=\"conversation.session.header.utilities\"] > [data-mobile-nav=\"header-info\"],\n [data-phase] header [data-slot=\"conversation.session.header.utilities\"] > [data-mobile-nav=\"header-workbench\"] {\n display: inline-flex !important;\n }\n\n /* Back button: 44pt touch target, leftmost column. */\n [data-mobile-nav=\"header-back\"] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 44px;\n height: 44px;\n padding: 0;\n border: none;\n background: transparent;\n color: var(--dsw-alias-label-primary, inherit);\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n [data-mobile-nav=\"header-back\"]:active {\n opacity: .6;\n }\n\n /* Info + workbench utility buttons, rightmost column. */\n [data-mobile-nav=\"header-info\"],\n [data-mobile-nav=\"header-workbench\"] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 34px;\n height: 34px;\n padding: 0;\n border: none;\n border-radius: 50%;\n background: transparent;\n color: var(--dsw-alias-label-secondary, inherit);\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n [data-mobile-nav=\"header-info\"]:active,\n [data-mobile-nav=\"header-workbench\"]:active {\n background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));\n }\n /* Icon family unification (real-device round 2, 2026-08-17): the \u24D8 text\n glyph is gone (MobileSessionHeader.tsx now renders IconInfoOutline16,\n a local 16px SVG built to match the primitives icon family) and the\n workbench button's IconPanelLeftOutline16 is mirrored into a\n panel-RIGHT glyph \u2014 there's no IconPanelRightOutline16 in primitives\n (checked lib/types/icons/index.d.ts), and the plugin's own right-side\n panel semantics are exactly the left icon flipped. Both buttons now\n carry a same-size (16px), same-stroke-weight icon. */\n [data-mobile-nav=\"header-workbench\"] svg {\n transform: scaleX(-1);\n }\n\n /* Official Chat/Trajectory tablist: removed from layout entirely (S2.1 \u2014\n visibility:hidden still held a 27px row, so the header carried the\n view-switch row's band TWICE: 72 + 27 + gap = 104px measured at 390px,\n and a real iPhone added ~54px of notch on top of that). The header's own\n padding-bottom above is now the row's seat.\n display:none does NOT break the view switch: HTMLElement.click()\n dispatches synthetically and fires React's handler regardless of\n rendering \u2014 only real pointer hit-testing needs a box, and this plugin\n never relies on it (MobileSessionHeader always calls .click()). */\n [data-phase] header [class$=\"_tabs\"][role=\"tablist\"] {\n display: none;\n }\n\n /* View-switch row: \"current view name + dots\", replacing the (still\n present, still clickable) official tablist visually. It renders inside\n headerActions/headerUtilities, but titleRow's own position:relative\n (set above) makes IT the containing block for absolute descendants \u2014\n display:contents on titleCluster does not break that search \u2014 so\n top:100% sits directly under the title regardless of the title row's\n actual height, with no hardcoded offset to keep in sync. */\n /* Activity chip (subagents / background tasks) rides the right end of the\n same band. It is a SIBLING of the switch button, not a child \u2014 that\n button spans the full row, so nesting would make every tap on the chip\n switch views too \u2014 and it shares titleRow as its containing block, so\n top:100% lands both on the same 28px line. z-index puts it above the\n row it overlaps. */\n [data-mobile-nav=\"header-activity\"] {\n position: absolute;\n right: 0;\n top: 100%;\n z-index: 1;\n display: inline-flex;\n align-items: center;\n gap: 9px;\n height: 28px;\n padding: 0 2px 0 10px;\n border: none;\n background: transparent;\n color: var(--dsw-alias-label-secondary, rgba(0, 0, 0, .5));\n font-family: inherit;\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n /* pointer-events:none so the invisible official trigger laid over this pill\n (effects/native-trigger-overlay.ts) is what a tap reaches. The pill is\n visuals only. */\n [data-mobile-nav=\"activity-pill\"] {\n pointer-events: none;\n display: inline-flex;\n align-items: center;\n gap: 3px;\n height: 28px;\n padding: 0;\n border: none;\n background: transparent;\n color: inherit;\n font-family: inherit;\n font-size: 12px;\n line-height: 18px;\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n [data-mobile-nav=\"activity-pill\"]:active {\n opacity: .55;\n }\n [data-mobile-nav=\"activity-count\"] {\n font-variant-numeric: tabular-nums;\n }\n /* Steady green = everything settled. */\n [data-mobile-nav=\"activity-dot\"] {\n display: block;\n width: 6px;\n height: 6px;\n margin-left: 1px;\n border-radius: 50%;\n background: var(--dsw-alias-state-success-primary, #16a34a);\n }\n /* A job that failed or was killed is not a success \u2014 never paint it green. */\n [data-activity-state=\"warning\"] [data-mobile-nav=\"activity-dot\"] {\n background: var(--dsw-alias-state-warn-primary, #d97706);\n }\n /* Pulsing blue = still running. Same state palette as the session dot. */\n [data-activity-state=\"running\"] [data-mobile-nav=\"activity-dot\"] {\n background: var(--dsw-alias-state-business-primary, #4f6ef7);\n animation: dsh-zen-activity-pulse 1.4s ease-in-out infinite;\n }\n @keyframes dsh-zen-activity-pulse {\n 0%, 100% { opacity: 1; transform: scale(1); }\n 50% { opacity: .45; transform: scale(.72); }\n }\n @media (prefers-reduced-motion: reduce) {\n [data-activity-state=\"running\"] [data-mobile-nav=\"activity-dot\"] {\n animation: none;\n }\n }\n\n [data-mobile-nav=\"header-viewrow\"] {\n position: absolute;\n left: 0;\n right: 0;\n top: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n height: 28px;\n padding: 0;\n border: none;\n background: transparent;\n color: var(--dsw-alias-label-secondary, rgba(0, 0, 0, .5));\n font-family: inherit;\n font-size: 13px;\n line-height: 18px;\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n [data-mobile-nav=\"header-viewrow-dots\"] {\n display: inline-flex;\n align-items: center;\n gap: 3px;\n }\n [data-mobile-nav=\"header-viewrow-dots\"] > i {\n display: block;\n width: 4px;\n height: 4px;\n border-radius: 50%;\n background: currentColor;\n opacity: .35;\n font-style: normal;\n }\n [data-mobile-nav=\"header-viewrow-dots\"] > i[data-active] {\n opacity: 1;\n }\n}\n";
2
2
  //# sourceMappingURL=header.css.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"header.css.d.ts","sourceRoot":"","sources":["../../../../src/client/styles/header.css.ts"],"names":[],"mappings":"AAiBA,eAAO,MAAM,UAAU,8rvBAogBtB,CAAA"}
1
+ {"version":3,"file":"header.css.d.ts","sourceRoot":"","sources":["../../../../src/client/styles/header.css.ts"],"names":[],"mappings":"AAsBA,eAAO,MAAM,UAAU,y3yBA6hBtB,CAAA"}
@@ -1,2 +1,2 @@
1
- export declare const LAYOUT_CSS = "/* ---------- mobile-only layout ---------- */\n\n@media (max-width: 1023px) {\n /* --- Phone chrome ---\n The system status bar stays visible (no fullscreen). Two adjustments\n make it behave:\n - touch-action: manipulation kills double-tap-to-zoom (and the 300ms\n tap delay) while keeping pan and pinch zoom; the client also\n suppresses legacy-iOS gesturestart as a fallback.\n - With the client's viewport-fit=cover, env(safe-area-inset-top) is the\n status bar / notch height; the rules below push the app content below\n it so the status bar never covers anything. Off notched phones (or in\n a normal browser tab where the layout viewport already sits below the\n status bar) the inset is 0 and nothing shifts. */\n html,\n body {\n touch-action: manipulation !important;\n }\n\n /* AppFrame: the drawer takes the sidebar column out of grid flow, so the\n remaining in-flow items (center, details) land in tracks 1..2: give the\n center every pixel and keep the details track at zero. The top padding\n clears the status bar / notch for every in-flow surface (session header,\n messages, composer); the absolutely-positioned drawer is unaffected (its\n containing block is the frame's padding box, i.e. still the frame top). */\n /* box-sizing: border-box is load-bearing (S2.1, 2026-08-17). The official\n frame carries height: 100% with the default content-box, so the\n safe-area padding ADDED 54px to its height instead of taking 54px out\n of the content area: the frame grew past the viewport, the document\n became scrollable by exactly the inset, and the very first scroll put\n the header right back under the notch \u2014 measured with\n ?mobile-nav-inset=54 (frame 898px tall inside an 844px root, html at\n y=-54). Invisible on every desktop browser because the inset is 0\n there, which is how it shipped. */\n [data-mobile-nav=\"frame\"] {\n position: relative !important;\n box-sizing: border-box !important;\n grid-template-columns: minmax(0, 1fr) 0 0 !important;\n padding-top: var(--mnav-sat) !important;\n }\n\n /* The sidebar column (first grid child) becomes a left drawer. The drawer\n hugs the sidebar content exactly (the wide sidebar carries an inline\n width, ~280px): a fixed 92vw box would leave a white strip where the\n container background shows beside the content.\n Closed state: translateX(-110%) \u2014 more than -100% of the max-content\n width \u2014 guarantees the whole drawer (and its shadow, had it one) leaves\n the viewport. A mere -100% leaves a sliver on screen; -105% (as used\n before) left 14px of the drawer plus a long 32px-blur shadow gradient\n visible along the left edge of the main UI. No box-shadow at all: the\n dimmed backdrop already separates drawer from content. */\n [data-mobile-nav=\"frame\"] > :first-child {\n position: absolute !important;\n inset: 0 auto 0 0 !important;\n width: max-content !important;\n max-width: 92vw !important;\n z-index: 40 !important;\n transform: translateX(-110%);\n transition: transform .28s var(--ds-ease-in-out, ease-in-out);\n background: var(--dsw-alias-bg-base, #ffffff);\n /* Keep the drawer's own content below the status bar / notch: the drawer\n spans the full frame height (its absolute containing block is the\n frame's padding box, so the frame's own safe-area padding does NOT\n reach it). The drawer background paints the status-bar strip, which\n the client's theme-color meta matches, so the strip reads seamless. */\n padding-top: var(--mnav-sat) !important;\n /* Kill the official sidebarCol right border: with the backdrop the edge\n reads cleanly, and the settings dialog (width:100% of this box) stays\n pixel-flush with the drawer. */\n border-right: none !important;\n }\n\n /* Expanded state (frame without data-sidebar-collapsed) slides the drawer in.\n The open state must be transform:none \u2014 NOT translateX(0): an identity\n transform still makes the drawer the containing block for fixed-position\n descendants (the settings dialog's .VOzbGW_overlay is portaled into the\n sidebar DOM). With the identity transform the wide settings sheet\n (100vw-16) overflows the 280px drawer, the dialog's focus scrolls the\n overflow:hidden drawer to scrollLeft=102, and every static child (plus the\n fixed overlay) shifts 102px off-screen. With transform:none the overlay is\n viewport-anchored: it dims the full screen and the sheet sits at left:8. */\n [data-mobile-nav=\"frame\"]:not([data-sidebar-collapsed]) > :first-child {\n transform: none !important;\n }\n\n /* Drag handles are useless on touch and would float over the drawer. */\n [data-side=\"sidebar\"],\n [data-side=\"details\"] {\n display: none !important;\n }\n\n /* --- Conversation text on mobile ---\n The official message flow keeps desktop's 32px side gutters and 16px\n type. On a phone: shrink the type a notch and widen the lines by\n trimming the gutters (the sidebar drawer list keeps its size). The\n flow's scroll container is the only _scroll element holding markdown\n <p> paragraphs \u2014 the composer's own scroll (textarea) is excluded\n via :has(p). */\n /* The official main scroll body reserves scrollbar-gutter for desktop\n scrollbars (8px), which shoves every column off-center on a phone.\n Classic desktop scrollbars (Edge/Chrome) also occupy ~8-17px in a\n phone-sized viewport, shifting the column further. Mobile scrolling\n is touch/wheel, so remove the scrollbar entirely on phones: the\n column is then exactly centered in every browser. */\n [data-phase] [class$=\"_scrollBody\"] {\n scrollbar-gutter: auto !important;\n scrollbar-width: none !important;\n }\n [data-phase] [class$=\"_scrollBody\"]::-webkit-scrollbar {\n display: none !important;\n width: 0 !important;\n height: 0 !important;\n }\n /* Message action rows (copy / run-time badges) can overflow the right\n edge on narrow screens \u2014 keep them inside the message width. */\n [data-phase] [class$=\"_actions\"] {\n overflow: hidden !important;\n }\n [data-phase] [class$=\"_actions\"] [class$=\"_timeEnd\"] {\n flex: 0 1 auto !important;\n min-width: 0 !important;\n overflow: hidden !important;\n text-overflow: ellipsis !important;\n white-space: nowrap !important;\n }\n\n [data-phase] [class$=\"_scroll\"]:has(p) {\n padding-left: 20px !important;\n padding-right: 20px !important;\n font-size: 15px !important;\n }\n /* The official markdown styles set an explicit 16px on paragraphs and\n list items, so the container's inherited 15px is not enough. User\n messages render their text in a div whose class carries _text_\n (16px too) \u2014 cover it as well. */\n [data-phase] [class$=\"_scroll\"]:has(p) p,\n [data-phase] [class$=\"_scroll\"]:has(p) li,\n [data-phase] [class$=\"_scroll\"]:has(p) [class*=\"_text_\"] {\n font-size: 15px !important;\n }\n\n /* Markdown tables: the official table uses width:max-content, so on a phone\n it hugs the content and leaves dead space beside/inside the table. Force\n the table to fill the message column and let the table wrapper handle\n overflow if a cell is genuinely too wide. */\n [data-phase] table {\n width: 100% !important;\n max-width: 100% !important;\n }\n [data-phase] th,\n [data-phase] td {\n max-width: none !important;\n min-width: 0 !important;\n }\n\n /* User bubbles: the official stack is capped at min(525px, 82%), which on a\n phone leaves a large blank strip on the left and pushes the bubble high.\n On mobile let the user message fill the same full width as assistant\n messages (the bubble background then spans the whole message column). */\n [data-phase] [class$=\"_userStack\"],\n [data-phase] [class$=\"_userStack\"] [class$=\"_bubble\"] {\n box-sizing: border-box !important;\n width: fit-content !important;\n max-width: 100% !important;\n }\n\n /* --- Composer bottom row on mobile ---\n The official row gives the model pill (trailing) flex:0 0 auto, which\n squeezes the agent-permission pill (modes) down to 15px: the pill's\n chevron then overflows on top of the model name. Let the permission\n pill keep its natural width and let the model pill shrink instead.\n Anchored by the composer card (:has(textarea)): row = the _row class\n containing a _trailing group, tools = its first child, permission pill\n = its 2nd child, model pill = the _trailing group.\n NOTE: do NOT anchor these by the card's :last-child \u2014 a client effect\n moves the git-graph branch chip INTO the card, which becomes the card's\n new last child and silently disables every :last-child rule (the model\n pill then falls back to the official fixed layout and long model IDs\n overflow instead of ellipsizing). Structural anchors only. */\n /* The official row pads every group with 12px gaps, which on a phone eats\n width the model pill needs for the full model ID. Tighten the row to 8px\n gaps (the modes pill keeps its natural width) so the model name gets\n every spare pixel. */\n [data-phase] [class*=\"_card\"]:has(textarea) [class$=\"_row\"]:has([class$=\"_trailing\"]) {\n gap: 8px !important;\n }\n [data-phase] [class*=\"_card\"]:has(textarea) [class$=\"_row\"]:has([class$=\"_trailing\"]) > :first-child {\n gap: 8px !important;\n }\n [data-phase] [class*=\"_card\"]:has(textarea) [class$=\"_row\"]:has([class$=\"_trailing\"]) > :first-child > :nth-child(2) {\n flex: 0 0 auto !important;\n gap: 8px !important;\n }\n [data-phase] [class*=\"_card\"]:has(textarea) [class$=\"_trailing\"] {\n flex: 1 1 auto !important;\n gap: 8px !important;\n min-width: 0 !important;\n }\n /* Let the model selector take the free space in the trailing group so the\n model name is not squeezed/truncated; the context meter stays fixed. */\n [data-phase] [class*=\"_card\"]:has(textarea) [class$=\"_root\"]:has(> [class$=\"_trigger\"][aria-haspopup=\"menu\"]) {\n flex: 1 1 auto !important;\n min-width: 0 !important;\n }\n [data-phase] [class*=\"_card\"]:has(textarea) [class$=\"_root\"]:has(> [class$=\"_trigger\"][aria-haspopup=\"menu\"]) > [class$=\"_trigger\"] {\n width: 100% !important;\n max-width: 100% !important;\n }\n /* The model label must absorb the trigger's free width: officially it is a\n fixed-content flex item, so the spare width of a grown pill sits unused\n between the chevron and the pill edge. Let the label grow and shrink:\n the full model ID shows whenever the row can fit it, and the ellipsis\n lands exactly at the available width otherwise. */\n [data-phase] [class*=\"_card\"]:has(textarea) [class$=\"_root\"]:has(> [class$=\"_trigger\"][aria-haspopup=\"menu\"]) > [class$=\"_trigger\"] > [class$=\"_triggerLabel\"] {\n flex: 1 1 auto !important;\n min-width: 0 !important;\n }\n [data-phase] [class*=\"_card\"]:has(textarea) [class$=\"_root\"]:has(> [class$=\"_trigger\"]:not([aria-haspopup=\"menu\"])) {\n flex: 0 0 auto !important;\n }\n\n /* Model switcher menu: the official dropdown is right-aligned to the\n trigger (right:0). The model pill sits near the center of the composer,\n so on a phone the 240px menu overflows the left edge and looks off-center.\n Center the menu on the trigger instead. */\n [data-phase] [class*=\"_card\"]:has(textarea) [class$=\"_root\"]:has(> [class$=\"_trigger\"]) > [class$=\"_menu\"] {\n left: 50% !important;\n right: auto !important;\n transform: translateX(-50%) !important;\n }\n\n /* --- Session header on mobile ---\n Layout goal: [toggle] [session title] [mode badge] in a row, with the\n Session log capsule removed from the header (relocated to the drawer\n footer). Stable structural hooks only:\n [data-phase] header the session header element\n header > :first-child titleRow (titleCluster + utilities)\n header > :first-child > :last-child headerUtilities (Session log seat) */\n [data-phase] header {\n padding-right: 12px !important;\n }\n /* Give the title row a lane clear of the absolutely-placed toggle, then\n balance the header: with header padding-right 12px, a 20px left\n padding puts the title's geometric center exactly on the viewport\n center (measured 195/195 at 390px). */\n [data-phase] header > :first-child {\n padding-left: 20px !important;\n }\n /* The directory toggle sits at the far left of the header (the header\n is position:relative; the data-slot wrappers are display:contents). */\n [data-mobile-nav=\"toggle\"] {\n position: absolute !important;\n left: 8px !important;\n top: 12px !important;\n z-index: 2 !important;\n }\n /* The Files action stays in-flow inside headerActions. On mobile headerActions\n does not expand to fill the row: it is pushed to the right edge and keeps\n its content size, so [mode] [subagent/jobs] [files] sit close together\n with the normal 8px gaps \u2014 compact, no artificial blank columns. Explicit\n flex order keeps Files rightmost regardless of DOM order. */\n [data-mobile-nav=\"files\"] {\n position: static !important;\n left: auto !important;\n right: auto !important;\n top: auto !important;\n z-index: auto !important;\n }\n [data-phase] header [class$=\"_headerActions\"] {\n flex: 0 1 auto !important;\n min-width: 0 !important;\n margin-left: auto !important;\n justify-content: flex-end !important;\n }\n /* The session title/crumb must yield space on mobile so the mode label and\n subagent cluster are not squeezed to a single letter. Cap it at ~24vw and\n let it truncate with ellipsis. */\n [data-phase] header [class$=\"_crumbs\"] {\n flex: 0 1 auto !important;\n min-width: 0 !important;\n max-width: 24vw !important;\n }\n /* Mode label (AgentPresetLabel): the official pill uses inline-flex, whose\n direct text node is only clipped \u2014 no ellipsis. Convert the pill to a\n block container, keep its icon absolutely positioned on the left, and let\n it size naturally up to 42vw so the mode name stays readable while the\n subagent/Files cluster still fits on a phone. */\n [data-phase] header [class$=\"_label\"]:has(> svg) {\n order: 1 !important;\n flex: 0 1 auto !important;\n min-width: 0 !important;\n max-width: 42vw !important;\n display: block !important;\n position: relative !important;\n box-sizing: border-box !important;\n padding-left: 18px !important;\n padding-right: 2px !important;\n overflow: hidden !important;\n text-overflow: ellipsis !important;\n white-space: nowrap !important;\n }\n [data-phase] header [class$=\"_label\"]:has(> svg) > svg {\n position: absolute !important;\n left: 0 !important;\n top: 50% !important;\n transform: translateY(-50%) !important;\n }\n /* Subagent/jobs helpers: keep their triggers on one line and never let the\n row grow artificial empty space. */\n [data-phase] header [class$=\"_root\"]:has(> button[class$=\"_trigger\"]) {\n order: 2 !important;\n flex: 0 0 auto !important;\n min-width: max-content !important;\n max-width: none !important;\n white-space: nowrap !important;\n /* On mobile the popover is anchored to the header (viewport-width\n containing block) instead of the trigger, so a small left inset keeps it\n fully on screen and never clips the tree's status dots. */\n position: static !important;\n }\n [data-phase] header [class$=\"_root\"]:has(> button[class$=\"_trigger\"]) > button,\n [data-phase] header [class$=\"_root\"]:has(> button[class$=\"_trigger\"]) > button * {\n white-space: nowrap !important;\n }\n [data-phase] header [data-mobile-nav=\"files\"] {\n order: 3 !important;\n flex: 0 0 auto !important;\n }\n /* Session log download: gone from the header row on mobile (the utilities\n seat holds only the session-log-export capsule). */\n [data-phase] header > :first-child > :last-child {\n display: none !important;\n }\n\n /* --- Header popovers on mobile (dsh-client-ui-jobs / dsh-client-ui-subagent) ---\n The official entries sit in the session header actions. Their popovers\n are anchored to the trigger's LEFT edge (left: 0), so from a right-edge\n trigger the 336px panel spills past the viewport. Re-anchor them to the\n trigger's right edge instead. The popover classes are hashed CSS-module\n names, so target them by the stable _menu suffix inside the header. */\n /* Header popovers on mobile: anchor to the left edge with a small inset and\n clamp the width, instead of right-aligning to a trigger near the right\n edge (which pushed the panel left off-screen and clipped the status dots).\n Also cap the height so the panel does not run into the composer/task bar. */\n [data-phase] header [class$=\"_menu\"] {\n left: 8px !important;\n right: auto !important;\n width: min(336px, calc(100vw - 16px)) !important;\n max-width: none !important;\n max-height: min(420px, calc(100dvh - 120px)) !important;\n }\n /* --- Settings dialog on mobile ---\n Desktop: 800px two-column flex (188px nav + content). Mobile: a\n near-full-width sheet \u2014 nav tabs wrap into rows on top, option rows\n stay horizontal (title+description left, control right). Structural\n selectors are scoped to the unique aria-modal dialog; every\n settings-specific rule is gated with\n :has(> :first-child > :last-child > button) \u2014 the settings nav tab\n list holds <button> tabs, so the transient export dialog (the same\n primitives Modal, header(title+close)+description+body) keeps its\n official centered card layout. Requires :has() support\n (Chromium 105+, 2022).\n\n The directory picker (dsh-client-ui-directory-picker-browse) must be\n excluded too: its footer bar holds <button> children AND its breadcrumb\n trail (role=\"navigation\") \u2014 which the role gate relies on to exclude\n it \u2014 is REPLACED by the path input in edit mode (pencil button), so\n without the ZuhsRW exclusion clicking the pencil would suddenly match\n this sheet rule: the dialog jumps to the top of the screen, the header\n (with the path input) is hidden by the > :first-child > :first-child\n display:none rule below, and the user can no longer type a path\n (issue #12, 2026-08-16). The picker family keeps the official layout\n on mobile in every mode. */\n [aria-modal=\"true\"]:has(> :first-child > :last-child > button):not(:has([role=\"navigation\"])):not(:has([class*=\"ZuhsRW\"])) {\n position: absolute !important;\n left: 8px !important;\n /* Fixed top (no translateY): a transform on the panel combined with the\n panel overflowing the max-content drawer shifts the fixed overlay's\n coordinate frame, dragging the whole sidebar content off-screen. The\n safe-area inset keeps the sheet below the status bar / notch. */\n top: calc(var(--mnav-sat) + 12px) !important;\n width: calc(100vw - 16px) !important;\n max-width: calc(100vw - 16px) !important;\n /* Height follows the content (no dead space under a short page); it\n caps at 100dvh-24 (less the safe-area top) and the options area\n scrolls only then. */\n height: auto !important;\n max-height: min(800px, calc(100vh - 24px - var(--mnav-sat))) !important;\n max-height: min(800px, calc(100dvh - 24px - var(--mnav-sat))) !important;\n flex-direction: column !important;\n border-radius: 14px !important;\n animation: dsh-mobile-nav-sheet-in .22s var(--ds-ease-out, ease-in-out);\n }\n /* The settings sheet's dimmed mask fades in with the panel (the mask is\n the first child of the overlay that directly contains the sheet). */\n :has(> [aria-modal=\"true\"]:has(> :first-child > :last-child > button):not(:has([role=\"navigation\"])):not(:has([class*=\"ZuhsRW\"]))) > :first-child {\n animation: dsh-mobile-nav-fade .18s var(--ds-ease-out, ease-in-out);\n }\n @media (prefers-reduced-motion: reduce) {\n [aria-modal=\"true\"]:has(> :first-child > :last-child > button):not(:has([role=\"navigation\"])):not(:has([class*=\"ZuhsRW\"])),\n :has(> [aria-modal=\"true\"]:has(> :first-child > :last-child > button):not(:has([role=\"navigation\"])):not(:has([class*=\"ZuhsRW\"]))) > :first-child {\n animation: none !important;\n }\n }\n /* The export dialog (not the settings sheet) must never overflow the\n viewport: the official centered card can be wider than 390px.\n :not([data-mobile-nav=\"info-sheet\"]) (S4, 2026-08-17): the session-info\n sheet also carries role=\"dialog\" aria-modal=\"true\" (correct a11y\n semantics for a bottom sheet with a scrim) and has no button as its\n first-child's last-child, so without this exclusion it silently matched\n this generic selector too \u2014 measured 358px (100vw-32px) instead of the\n 374px its own left:8px/right:8px margins specify. Same category of bug\n as the ZuhsRW directory-picker collision below; same fix shape. */\n [aria-modal=\"true\"]:not(:has(> :first-child > :last-child > button)):not([data-mobile-nav=\"info-sheet\"]) {\n max-width: calc(100vw - 32px) !important;\n }\n /* Nav bar: hide the \"Settings\" caption (redundant on a full-width sheet)\n and wrap the tab list so every tab is visible \u2014 a horizontal scroll cut\n the last tab (\"Plugins\") off with no affordance to scroll. */\n [aria-modal=\"true\"]:has(> :first-child > :last-child > button):not(:has([role=\"navigation\"])):not(:has([class*=\"ZuhsRW\"])) > :first-child {\n width: 100% !important;\n flex-direction: row !important;\n align-items: center !important;\n gap: 6px !important;\n padding: 10px 12px 8px !important;\n }\n [aria-modal=\"true\"]:has(> :first-child > :last-child > button):not(:has([role=\"navigation\"])):not(:has([class*=\"ZuhsRW\"])) > :first-child > :first-child {\n display: none !important;\n }\n /* The tab list scrolls in the space left by the toolbar: the toolbar\n (config file + close) is reparented INTO this nav row by a client\n effect (MobileNavOverlay), so the tab list must be anchored by its\n class, NOT by :last-child (the reparented toolbar becomes the nav's\n new last child). */\n [aria-modal=\"true\"]:has(> :first-child > :last-child > button):not(:has([role=\"navigation\"])):not(:has([class*=\"ZuhsRW\"])) > :first-child [class$=\"_navList\"] {\n flex: 1 1 auto !important;\n min-width: 0 !important;\n flex-direction: row !important;\n flex-wrap: wrap !important;\n gap: 6px !important;\n overflow: visible !important;\n }\n /* Content toolbar (Open configuration file + close): grouped flush to\n the right edge, and reparented INTO the nav row on mobile so it shares\n one line with the tabs (user feedback 2026-08-16 \u2014 the toolbar's own\n row left a full-width dead gap under the tabs). Anchored by class: the\n header leaves the content subtree, so :first-child/:last-child anchors\n would now hit the options area. Children carry official auto-margins\n that would defeat flex-end, so neutralize them. The close button gets\n a round tappable base so it reads as its own control, not part of the\n outline button. */\n [aria-modal=\"true\"]:has(> :first-child > :last-child > button):not(:has([role=\"navigation\"])):not(:has([class*=\"ZuhsRW\"])) [class$=\"_header\"] {\n flex: 0 0 auto !important;\n justify-content: flex-end !important;\n align-items: center !important;\n gap: 8px !important;\n padding: 0 0 0 4px !important;\n min-height: 40px !important;\n }\n [aria-modal=\"true\"]:has(> :first-child > :last-child > button):not(:has([role=\"navigation\"])):not(:has([class*=\"ZuhsRW\"])) [class$=\"_header\"] > * {\n margin-left: 0 !important;\n margin-right: 0 !important;\n }\n [aria-modal=\"true\"]:has(> :first-child > :last-child > button):not(:has([role=\"navigation\"])):not(:has([class*=\"ZuhsRW\"])) [class$=\"_header\"] > :last-child {\n width: 32px !important;\n height: 32px !important;\n border-radius: 50% !important;\n display: inline-flex !important;\n align-items: center !important;\n justify-content: center !important;\n background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06)) !important;\n }\n /* Appearance mode cards: the official cube row renders three tall\n vertical cards (~268px) that eat half the sheet. Turn them into a\n compact horizontal trio (icon + label inline, equal widths).\n Relies on the official cube-row class name of this version. */\n [aria-modal=\"true\"] [class$=\"_cubeRow\"] {\n gap: 6px !important;\n }\n [aria-modal=\"true\"] [class$=\"_cubeRow\"] > * {\n flex: 1 1 0 !important;\n flex-direction: row !important;\n align-items: center !important;\n justify-content: center !important;\n gap: 6px !important;\n padding: 10px 8px !important;\n min-height: 0 !important;\n }\n /* Content: the options scroll area gets bottom breathing room so the last\n row never sits flush against the sheet's rounded corner. */\n [aria-modal=\"true\"]:has(> :first-child > :last-child > button):not(:has([role=\"navigation\"])):not(:has([class*=\"ZuhsRW\"])) > :last-child {\n flex: 1 1 auto !important;\n min-height: 0 !important;\n }\n [aria-modal=\"true\"]:has(> :first-child > :last-child > button):not(:has([role=\"navigation\"])):not(:has([class*=\"ZuhsRW\"])) > :last-child > :last-child {\n padding: 0 12px 24px !important;\n }\n";
1
+ export declare const LAYOUT_CSS = "/* ---------- mobile-only layout ---------- */\n\n@media (max-width: 1023px) {\n /* --- Phone chrome ---\n The system status bar stays visible (no fullscreen). Two adjustments\n make it behave:\n - touch-action: manipulation kills double-tap-to-zoom (and the 300ms\n tap delay) while keeping pan and pinch zoom; the client also\n suppresses legacy-iOS gesturestart as a fallback.\n - With the client's viewport-fit=cover, env(safe-area-inset-top) is the\n status bar / notch height; the rules below push the app content below\n it so the status bar never covers anything. Off notched phones (or in\n a normal browser tab where the layout viewport already sits below the\n status bar) the inset is 0 and nothing shifts. */\n html,\n body {\n touch-action: manipulation !important;\n }\n\n /* AppFrame: the drawer takes the sidebar column out of grid flow, so the\n remaining in-flow items (center, details) land in tracks 1..2: give the\n center every pixel and keep the details track at zero. The top padding\n clears the status bar / notch for every in-flow surface (session header,\n messages, composer); the absolutely-positioned drawer is unaffected (its\n containing block is the frame's padding box, i.e. still the frame top). */\n /* box-sizing: border-box is load-bearing (S2.1, 2026-08-17). The official\n frame carries height: 100% with the default content-box, so the\n safe-area padding ADDED 54px to its height instead of taking 54px out\n of the content area: the frame grew past the viewport, the document\n became scrollable by exactly the inset, and the very first scroll put\n the header right back under the notch \u2014 measured with\n ?mobile-nav-inset=54 (frame 898px tall inside an 844px root, html at\n y=-54). Invisible on every desktop browser because the inset is 0\n there, which is how it shipped. */\n [data-mobile-nav=\"frame\"] {\n position: relative !important;\n box-sizing: border-box !important;\n grid-template-columns: minmax(0, 1fr) 0 0 !important;\n padding-top: var(--mnav-sat) !important;\n }\n\n /* The sidebar column (first grid child) becomes a left drawer. The drawer\n hugs the sidebar content exactly (the wide sidebar carries an inline\n width, ~280px): a fixed 92vw box would leave a white strip where the\n container background shows beside the content.\n Closed state: translateX(-110%) \u2014 more than -100% of the max-content\n width \u2014 guarantees the whole drawer (and its shadow, had it one) leaves\n the viewport. A mere -100% leaves a sliver on screen; -105% (as used\n before) left 14px of the drawer plus a long 32px-blur shadow gradient\n visible along the left edge of the main UI. No box-shadow at all: the\n dimmed backdrop already separates drawer from content. */\n [data-mobile-nav=\"frame\"] > :first-child {\n position: absolute !important;\n inset: 0 auto 0 0 !important;\n width: max-content !important;\n max-width: 92vw !important;\n z-index: 40 !important;\n transform: translateX(-110%);\n transition: transform .28s var(--ds-ease-in-out, ease-in-out);\n background: var(--dsw-alias-bg-base, #ffffff);\n /* Keep the drawer's own content below the status bar / notch: the drawer\n spans the full frame height (its absolute containing block is the\n frame's padding box, so the frame's own safe-area padding does NOT\n reach it). The drawer background paints the status-bar strip, which\n the client's theme-color meta matches, so the strip reads seamless. */\n padding-top: var(--mnav-sat) !important;\n /* Kill the official sidebarCol right border: with the backdrop the edge\n reads cleanly, and the settings dialog (width:100% of this box) stays\n pixel-flush with the drawer. */\n border-right: none !important;\n }\n\n /* Expanded state (frame without data-sidebar-collapsed) slides the drawer in.\n The open state must be transform:none \u2014 NOT translateX(0): an identity\n transform still makes the drawer the containing block for fixed-position\n descendants (the settings dialog's .VOzbGW_overlay is portaled into the\n sidebar DOM). With the identity transform the wide settings sheet\n (100vw-16) overflows the 280px drawer, the dialog's focus scrolls the\n overflow:hidden drawer to scrollLeft=102, and every static child (plus the\n fixed overlay) shifts 102px off-screen. With transform:none the overlay is\n viewport-anchored: it dims the full screen and the sheet sits at left:8. */\n [data-mobile-nav=\"frame\"]:not([data-sidebar-collapsed]) > :first-child {\n transform: none !important;\n }\n\n /* Drag handles are useless on touch and would float over the drawer. */\n [data-side=\"sidebar\"],\n [data-side=\"details\"] {\n display: none !important;\n }\n\n /* --- Conversation text on mobile ---\n The official message flow keeps desktop's 32px side gutters and 16px\n type. On a phone: shrink the type a notch and widen the lines by\n trimming the gutters (the sidebar drawer list keeps its size). The\n flow's scroll container is the only _scroll element holding markdown\n <p> paragraphs \u2014 the composer's own scroll (textarea) is excluded\n via :has(p). */\n /* The official main scroll body reserves scrollbar-gutter for desktop\n scrollbars (8px), which shoves every column off-center on a phone.\n Classic desktop scrollbars (Edge/Chrome) also occupy ~8-17px in a\n phone-sized viewport, shifting the column further. Mobile scrolling\n is touch/wheel, so remove the scrollbar entirely on phones: the\n column is then exactly centered in every browser. */\n [data-phase] [class$=\"_scrollBody\"] {\n scrollbar-gutter: auto !important;\n scrollbar-width: none !important;\n }\n [data-phase] [class$=\"_scrollBody\"]::-webkit-scrollbar {\n display: none !important;\n width: 0 !important;\n height: 0 !important;\n }\n /* Message action rows (copy / run-time badges) can overflow the right\n edge on narrow screens \u2014 keep them inside the message width. */\n [data-phase] [class$=\"_actions\"] {\n overflow: hidden !important;\n }\n [data-phase] [class$=\"_actions\"] [class$=\"_timeEnd\"] {\n flex: 0 1 auto !important;\n min-width: 0 !important;\n overflow: hidden !important;\n text-overflow: ellipsis !important;\n white-space: nowrap !important;\n }\n\n [data-phase] [class$=\"_scroll\"]:has(p) {\n padding-left: 20px !important;\n padding-right: 20px !important;\n font-size: 15px !important;\n }\n /* The official markdown styles set an explicit 16px on paragraphs and\n list items, so the container's inherited 15px is not enough. User\n messages render their text in a div whose class carries _text_\n (16px too) \u2014 cover it as well. */\n [data-phase] [class$=\"_scroll\"]:has(p) p,\n [data-phase] [class$=\"_scroll\"]:has(p) li,\n [data-phase] [class$=\"_scroll\"]:has(p) [class*=\"_text_\"] {\n font-size: 15px !important;\n }\n\n /* Markdown tables: the official table uses width:max-content, so on a phone\n it hugs the content and leaves dead space beside/inside the table. Force\n the table to fill the message column and let the table wrapper handle\n overflow if a cell is genuinely too wide. */\n [data-phase] table {\n width: 100% !important;\n max-width: 100% !important;\n }\n [data-phase] th,\n [data-phase] td {\n max-width: none !important;\n min-width: 0 !important;\n }\n\n /* User bubbles: the official stack is capped at min(525px, 82%), which on a\n phone leaves a large blank strip on the left and pushes the bubble high.\n On mobile let the user message fill the same full width as assistant\n messages (the bubble background then spans the whole message column). */\n [data-phase] [class$=\"_userStack\"],\n [data-phase] [class$=\"_userStack\"] [class$=\"_bubble\"] {\n box-sizing: border-box !important;\n width: fit-content !important;\n max-width: 100% !important;\n }\n\n /* --- Composer bottom row on mobile ---\n The official row gives the model pill (trailing) flex:0 0 auto, which\n squeezes the agent-permission pill (modes) down to 15px: the pill's\n chevron then overflows on top of the model name. Let the permission\n pill keep its natural width and let the model pill shrink instead.\n Anchored by the composer card (:has(textarea)): row = the _row class\n containing a _trailing group, tools = its first child, permission pill\n = its 2nd child, model pill = the _trailing group.\n NOTE: do NOT anchor these by the card's :last-child \u2014 a client effect\n moves the git-graph branch chip INTO the card, which becomes the card's\n new last child and silently disables every :last-child rule (the model\n pill then falls back to the official fixed layout and long model IDs\n overflow instead of ellipsizing). Structural anchors only. */\n /* The official row pads every group with 12px gaps, which on a phone eats\n width the model pill needs for the full model ID. Tighten the row to 8px\n gaps (the modes pill keeps its natural width) so the model name gets\n every spare pixel. */\n [data-phase] [class*=\"_card\"]:has(textarea) [class$=\"_row\"]:has([class$=\"_trailing\"]) {\n gap: 8px !important;\n }\n [data-phase] [class*=\"_card\"]:has(textarea) [class$=\"_row\"]:has([class$=\"_trailing\"]) > :first-child {\n gap: 8px !important;\n }\n [data-phase] [class*=\"_card\"]:has(textarea) [class$=\"_row\"]:has([class$=\"_trailing\"]) > :first-child > :nth-child(2) {\n flex: 0 0 auto !important;\n gap: 8px !important;\n }\n [data-phase] [class*=\"_card\"]:has(textarea) [class$=\"_trailing\"] {\n flex: 1 1 auto !important;\n gap: 8px !important;\n min-width: 0 !important;\n }\n /* Let the model selector take the free space in the trailing group so the\n model name is not squeezed/truncated; the context meter stays fixed. */\n [data-phase] [class*=\"_card\"]:has(textarea) [class$=\"_root\"]:has(> [class$=\"_trigger\"][aria-haspopup=\"menu\"]) {\n flex: 1 1 auto !important;\n min-width: 0 !important;\n }\n [data-phase] [class*=\"_card\"]:has(textarea) [class$=\"_root\"]:has(> [class$=\"_trigger\"][aria-haspopup=\"menu\"]) > [class$=\"_trigger\"] {\n width: 100% !important;\n max-width: 100% !important;\n }\n /* The model label must absorb the trigger's free width: officially it is a\n fixed-content flex item, so the spare width of a grown pill sits unused\n between the chevron and the pill edge. Let the label grow and shrink:\n the full model ID shows whenever the row can fit it, and the ellipsis\n lands exactly at the available width otherwise. */\n [data-phase] [class*=\"_card\"]:has(textarea) [class$=\"_root\"]:has(> [class$=\"_trigger\"][aria-haspopup=\"menu\"]) > [class$=\"_trigger\"] > [class$=\"_triggerLabel\"] {\n flex: 1 1 auto !important;\n min-width: 0 !important;\n }\n [data-phase] [class*=\"_card\"]:has(textarea) [class$=\"_root\"]:has(> [class$=\"_trigger\"]:not([aria-haspopup=\"menu\"])) {\n flex: 0 0 auto !important;\n }\n\n /* Model switcher menu: the official dropdown is right-aligned to the\n trigger (right:0). The model pill sits near the center of the composer,\n so on a phone the 240px menu overflows the left edge and looks off-center.\n Center the menu on the trigger instead. */\n [data-phase] [class*=\"_card\"]:has(textarea) [class$=\"_root\"]:has(> [class$=\"_trigger\"]) > [class$=\"_menu\"] {\n left: 50% !important;\n right: auto !important;\n transform: translateX(-50%) !important;\n }\n\n /* --- Session header on mobile ---\n Layout goal: [toggle] [session title] [mode badge] in a row, with the\n Session log capsule removed from the header (relocated to the drawer\n footer). Stable structural hooks only:\n [data-phase] header the session header element\n header > :first-child titleRow (titleCluster + utilities)\n header > :first-child > :last-child headerUtilities (Session log seat) */\n [data-phase] header {\n padding-right: 12px !important;\n }\n /* Give the title row a lane clear of the absolutely-placed toggle, then\n balance the header: with header padding-right 12px, a 20px left\n padding puts the title's geometric center exactly on the viewport\n center (measured 195/195 at 390px). */\n [data-phase] header > :first-child {\n padding-left: 20px !important;\n }\n /* The directory toggle sits at the far left of the header (the header\n is position:relative; the data-slot wrappers are display:contents). */\n [data-mobile-nav=\"toggle\"] {\n position: absolute !important;\n left: 8px !important;\n top: 12px !important;\n z-index: 2 !important;\n }\n /* The Files action stays in-flow inside headerActions. On mobile headerActions\n does not expand to fill the row: it is pushed to the right edge and keeps\n its content size, so [mode] [subagent/jobs] [files] sit close together\n with the normal 8px gaps \u2014 compact, no artificial blank columns. Explicit\n flex order keeps Files rightmost regardless of DOM order. */\n [data-mobile-nav=\"files\"] {\n position: static !important;\n left: auto !important;\n right: auto !important;\n top: auto !important;\n z-index: auto !important;\n }\n [data-phase] header [class$=\"_headerActions\"] {\n flex: 0 1 auto !important;\n min-width: 0 !important;\n margin-left: auto !important;\n justify-content: flex-end !important;\n }\n /* The session title/crumb must yield space on mobile so the mode label and\n subagent cluster are not squeezed to a single letter. Cap it at ~24vw and\n let it truncate with ellipsis. */\n [data-phase] header [class$=\"_crumbs\"] {\n flex: 0 1 auto !important;\n min-width: 0 !important;\n max-width: 24vw !important;\n }\n /* Mode label (AgentPresetLabel): the official pill uses inline-flex, whose\n direct text node is only clipped \u2014 no ellipsis. Convert the pill to a\n block container, keep its icon absolutely positioned on the left, and let\n it size naturally up to 42vw so the mode name stays readable while the\n subagent/Files cluster still fits on a phone. */\n [data-phase] header [class$=\"_label\"]:has(> svg) {\n order: 1 !important;\n flex: 0 1 auto !important;\n min-width: 0 !important;\n max-width: 42vw !important;\n display: block !important;\n position: relative !important;\n box-sizing: border-box !important;\n padding-left: 18px !important;\n padding-right: 2px !important;\n overflow: hidden !important;\n text-overflow: ellipsis !important;\n white-space: nowrap !important;\n }\n [data-phase] header [class$=\"_label\"]:has(> svg) > svg {\n position: absolute !important;\n left: 0 !important;\n top: 50% !important;\n transform: translateY(-50%) !important;\n }\n /* Subagent/jobs helpers: keep their triggers on one line and never let the\n row grow artificial empty space. */\n [data-phase] header [class$=\"_root\"]:has(> button[class$=\"_trigger\"]) {\n order: 2 !important;\n flex: 0 0 auto !important;\n min-width: max-content !important;\n max-width: none !important;\n white-space: nowrap !important;\n /* On mobile the popover is anchored to the header (viewport-width\n containing block) instead of the trigger, so a small left inset keeps it\n fully on screen and never clips the tree's status dots. */\n position: static !important;\n }\n [data-phase] header [class$=\"_root\"]:has(> button[class$=\"_trigger\"]) > button,\n [data-phase] header [class$=\"_root\"]:has(> button[class$=\"_trigger\"]) > button * {\n white-space: nowrap !important;\n }\n [data-phase] header [data-mobile-nav=\"files\"] {\n order: 3 !important;\n flex: 0 0 auto !important;\n }\n /* Session log download: gone from the header row on mobile (the utilities\n seat holds only the session-log-export capsule). */\n [data-phase] header > :first-child > :last-child {\n display: none !important;\n }\n\n /* --- Header popovers on mobile (dsh-client-ui-jobs / dsh-client-ui-subagent) ---\n The official entries sit in the session header actions. Their popovers\n are anchored to the trigger's LEFT edge (left: 0), so from a right-edge\n trigger the 336px panel spills past the viewport. Re-anchor them to the\n trigger's right edge instead. The popover classes are hashed CSS-module\n names, so target them by the stable _menu suffix inside the header. */\n /* Header popovers on mobile: anchor to the left edge with a small inset and\n clamp the width, instead of right-aligning to a trigger near the right\n edge (which pushed the panel left off-screen and clipped the status dots).\n Also cap the height so the panel does not run into the composer/task bar. */\n [data-phase] header [class$=\"_menu\"] {\n left: 8px !important;\n right: auto !important;\n width: min(336px, calc(100vw - 16px)) !important;\n max-width: none !important;\n max-height: min(420px, calc(100dvh - 120px)) !important;\n }\n /* --- Settings dialog on mobile ---\n Desktop: 800px two-column flex (188px nav + content). Mobile: a\n near-full-width sheet \u2014 nav tabs wrap into rows on top, option rows\n stay horizontal (title+description left, control right). Structural\n selectors are scoped to the unique aria-modal dialog; every\n settings-specific rule is gated with\n :has(> :first-child > :last-child > button) \u2014 the settings nav tab\n list holds <button> tabs, so the transient export dialog (the same\n primitives Modal, header(title+close)+description+body) keeps its\n official centered card layout. Requires :has() support\n (Chromium 105+, 2022).\n\n The directory picker (dsh-client-ui-directory-picker-browse) must be\n excluded too: its footer bar holds <button> children AND its breadcrumb\n trail (role=\"navigation\") \u2014 which the role gate relies on to exclude\n it \u2014 is REPLACED by the path input in edit mode (pencil button), so\n without the ZuhsRW exclusion clicking the pencil would suddenly match\n this sheet rule: the dialog jumps to the top of the screen, the header\n (with the path input) is hidden by the > :first-child > :first-child\n display:none rule below, and the user can no longer type a path\n (issue #12, 2026-08-16). The picker family keeps the official layout\n on mobile in every mode. */\n [aria-modal=\"true\"]:has(> :first-child > :last-child > button):not(:has([role=\"navigation\"])):not(:has([class*=\"ZuhsRW\"])) {\n position: absolute !important;\n left: 8px !important;\n /* Fixed top (no translateY): a transform on the panel combined with the\n panel overflowing the max-content drawer shifts the fixed overlay's\n coordinate frame, dragging the whole sidebar content off-screen. The\n safe-area inset keeps the sheet below the status bar / notch. */\n top: calc(var(--mnav-sat) + 12px) !important;\n width: calc(100vw - 16px) !important;\n max-width: calc(100vw - 16px) !important;\n /* Height follows the content (no dead space under a short page); it\n caps at 100dvh-24 (less the safe-area top) and the options area\n scrolls only then. */\n height: auto !important;\n max-height: min(800px, calc(100vh - 24px - var(--mnav-sat))) !important;\n max-height: min(800px, calc(100dvh - 24px - var(--mnav-sat))) !important;\n flex-direction: column !important;\n border-radius: 14px !important;\n animation: dsh-mobile-nav-sheet-in .22s var(--ds-ease-out, ease-in-out);\n }\n /* The settings sheet's dimmed mask fades in with the panel (the mask is\n the first child of the overlay that directly contains the sheet). */\n :has(> [aria-modal=\"true\"]:has(> :first-child > :last-child > button):not(:has([role=\"navigation\"])):not(:has([class*=\"ZuhsRW\"]))) > :first-child {\n animation: dsh-mobile-nav-fade .18s var(--ds-ease-out, ease-in-out);\n }\n @media (prefers-reduced-motion: reduce) {\n [aria-modal=\"true\"]:has(> :first-child > :last-child > button):not(:has([role=\"navigation\"])):not(:has([class*=\"ZuhsRW\"])),\n :has(> [aria-modal=\"true\"]:has(> :first-child > :last-child > button):not(:has([role=\"navigation\"])):not(:has([class*=\"ZuhsRW\"]))) > :first-child {\n animation: none !important;\n }\n }\n /* The export dialog (not the settings sheet) must never overflow the\n viewport: the official centered card can be wider than 390px.\n :not([data-mobile-nav=\"info-sheet\"]) (S4, 2026-08-17): the session-info\n sheet also carries role=\"dialog\" aria-modal=\"true\" (correct a11y\n semantics for a bottom sheet with a scrim) and has no button as its\n first-child's last-child, so without this exclusion it silently matched\n this generic selector too \u2014 measured 358px (100vw-32px) instead of the\n 374px its own left:8px/right:8px margins specify. Same category of bug\n as the ZuhsRW directory-picker collision below; same fix shape.\n :not([data-mobile-nav=\"share-range-sheet\"]) (share-image, 2026-09-10):\n the share range picker rides the same role=\"dialog\" aria-modal bottom\n -sheet shape and the same margins, so it hit the identical clamp. */\n [aria-modal=\"true\"]:not(:has(> :first-child > :last-child > button)):not([data-mobile-nav=\"info-sheet\"]):not([data-mobile-nav=\"share-range-sheet\"]) {\n max-width: calc(100vw - 32px) !important;\n }\n /* Nav bar: hide the \"Settings\" caption (redundant on a full-width sheet)\n and wrap the tab list so every tab is visible \u2014 a horizontal scroll cut\n the last tab (\"Plugins\") off with no affordance to scroll. */\n [aria-modal=\"true\"]:has(> :first-child > :last-child > button):not(:has([role=\"navigation\"])):not(:has([class*=\"ZuhsRW\"])) > :first-child {\n width: 100% !important;\n flex-direction: row !important;\n align-items: center !important;\n gap: 6px !important;\n padding: 10px 12px 8px !important;\n }\n [aria-modal=\"true\"]:has(> :first-child > :last-child > button):not(:has([role=\"navigation\"])):not(:has([class*=\"ZuhsRW\"])) > :first-child > :first-child {\n display: none !important;\n }\n /* The tab list scrolls in the space left by the toolbar: the toolbar\n (config file + close) is reparented INTO this nav row by a client\n effect (MobileNavOverlay), so the tab list must be anchored by its\n class, NOT by :last-child (the reparented toolbar becomes the nav's\n new last child). */\n [aria-modal=\"true\"]:has(> :first-child > :last-child > button):not(:has([role=\"navigation\"])):not(:has([class*=\"ZuhsRW\"])) > :first-child [class$=\"_navList\"] {\n flex: 1 1 auto !important;\n min-width: 0 !important;\n flex-direction: row !important;\n flex-wrap: wrap !important;\n gap: 6px !important;\n overflow: visible !important;\n }\n /* Content toolbar (Open configuration file + close): grouped flush to\n the right edge, and reparented INTO the nav row on mobile so it shares\n one line with the tabs (user feedback 2026-08-16 \u2014 the toolbar's own\n row left a full-width dead gap under the tabs). Anchored by class: the\n header leaves the content subtree, so :first-child/:last-child anchors\n would now hit the options area. Children carry official auto-margins\n that would defeat flex-end, so neutralize them. The close button gets\n a round tappable base so it reads as its own control, not part of the\n outline button. */\n [aria-modal=\"true\"]:has(> :first-child > :last-child > button):not(:has([role=\"navigation\"])):not(:has([class*=\"ZuhsRW\"])) [class$=\"_header\"] {\n flex: 0 0 auto !important;\n justify-content: flex-end !important;\n align-items: center !important;\n gap: 8px !important;\n padding: 0 0 0 4px !important;\n min-height: 40px !important;\n }\n [aria-modal=\"true\"]:has(> :first-child > :last-child > button):not(:has([role=\"navigation\"])):not(:has([class*=\"ZuhsRW\"])) [class$=\"_header\"] > * {\n margin-left: 0 !important;\n margin-right: 0 !important;\n }\n [aria-modal=\"true\"]:has(> :first-child > :last-child > button):not(:has([role=\"navigation\"])):not(:has([class*=\"ZuhsRW\"])) [class$=\"_header\"] > :last-child {\n width: 32px !important;\n height: 32px !important;\n border-radius: 50% !important;\n display: inline-flex !important;\n align-items: center !important;\n justify-content: center !important;\n background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06)) !important;\n }\n /* Appearance mode cards: the official cube row renders three tall\n vertical cards (~268px) that eat half the sheet. Turn them into a\n compact horizontal trio (icon + label inline, equal widths).\n Relies on the official cube-row class name of this version. */\n [aria-modal=\"true\"] [class$=\"_cubeRow\"] {\n gap: 6px !important;\n }\n [aria-modal=\"true\"] [class$=\"_cubeRow\"] > * {\n flex: 1 1 0 !important;\n flex-direction: row !important;\n align-items: center !important;\n justify-content: center !important;\n gap: 6px !important;\n padding: 10px 8px !important;\n min-height: 0 !important;\n }\n /* Content: the options scroll area gets bottom breathing room so the last\n row never sits flush against the sheet's rounded corner. */\n [aria-modal=\"true\"]:has(> :first-child > :last-child > button):not(:has([role=\"navigation\"])):not(:has([class*=\"ZuhsRW\"])) > :last-child {\n flex: 1 1 auto !important;\n min-height: 0 !important;\n }\n [aria-modal=\"true\"]:has(> :first-child > :last-child > button):not(:has([role=\"navigation\"])):not(:has([class*=\"ZuhsRW\"])) > :last-child > :last-child {\n padding: 0 12px 24px !important;\n }\n";
2
2
  //# sourceMappingURL=layout.css.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"layout.css.d.ts","sourceRoot":"","sources":["../../../../src/client/styles/layout.css.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,UAAU,m6yBAiftB,CAAA"}
1
+ {"version":3,"file":"layout.css.d.ts","sourceRoot":"","sources":["../../../../src/client/styles/layout.css.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,UAAU,qrzBAoftB,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAEhE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAIlD,6DAA6D;AAC7D,eAAO,MAAM,YAAY,4BAA4B,CAAA;AAErD;;kEAEkE;AAClE,eAAO,MAAM,mBAAmB,mCAAmC,CAAA;AAEnE,+FAA+F;AAC/F,eAAO,MAAM,UAAU,iBAAiB,CAAA;AAExC,6DAA6D;AAC7D,eAAO,MAAM,wBAAwB,QAAmB,CAAA;AAQxD,wBAAwB;AACxB,MAAM,WAAW,eAAe;IAC9B,iGAAiG;IACjG,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;mEAE+D;IAC/D,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB;;;;;;;gCAO4B;IAC5B,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,+EAA+E;IAC/E,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B;yCACqC;IACrC,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAC7B;AAED;;;;;;;;;GASG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAGhH;AAwCD;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAa5D;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAKnE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAgBlD;AA6HD;;;;;;;;;GASG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC,IAAI,CAAC,CA+Bf;AAED;;;;;;;GAOG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,GAAE,eAAoB,GAAG,IAAI,CA8BtE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAEhE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAKlD,6DAA6D;AAC7D,eAAO,MAAM,YAAY,4BAA4B,CAAA;AAErD;;kEAEkE;AAClE,eAAO,MAAM,mBAAmB,mCAAmC,CAAA;AAEnE,+FAA+F;AAC/F,eAAO,MAAM,UAAU,iBAAiB,CAAA;AAExC,6DAA6D;AAC7D,eAAO,MAAM,wBAAwB,QAAmB,CAAA;AAQxD,wBAAwB;AACxB,MAAM,WAAW,eAAe;IAC9B,iGAAiG;IACjG,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;mEAE+D;IAC/D,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB;;;;;;;gCAO4B;IAC5B,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,+EAA+E;IAC/E,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B;yCACqC;IACrC,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAC7B;AAED;;;;;;;;;GASG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAGhH;AAwCD;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAa5D;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAKnE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAgBlD;AA6HD;;;;;;;;;GASG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC,IAAI,CAAC,CA+Bf;AAED;;;;;;;GAOG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,GAAE,eAAoB,GAAG,IAAI,CA0CtE"}