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
@@ -20,6 +20,7 @@ export declare const zh: {
20
20
  readonly sessionInfo: "会话信息";
21
21
  readonly workbench: "工作台";
22
22
  readonly workbenchClose: "关闭工作台";
23
+ readonly sidebar: "侧边栏";
23
24
  readonly switchView: "切换视图";
24
25
  readonly attach: "添加附件";
25
26
  readonly attachBusy: "上传中…";
@@ -47,6 +48,28 @@ export declare const zh: {
47
48
  readonly infoActionError: "操作失败:{message}";
48
49
  readonly turnFold: "过程 · {count} 步";
49
50
  readonly turnFoldRunning: "进行中 · {count} 步";
51
+ readonly shareCardTurns: "{count} 轮对话";
52
+ readonly shareCardGeneratedBy: "由 DeepSeek Harness 生成";
53
+ readonly shareCardImage: "图片附件";
54
+ readonly shareAction: "分享图";
55
+ readonly sharePickerTitle: "分享范围";
56
+ readonly sharePickerAll: "全部对话";
57
+ readonly sharePickerLast: "最近 {count} 轮";
58
+ readonly sharePickerConfirm: "生成分享图";
59
+ readonly sharePickerBusy: "生成中…";
60
+ readonly shareTruncatedNote: "内容过长,已省略前 {count} 轮";
61
+ readonly shareErrRouteMissing: "当前 DSH 版本未提供分享图接口,请更新 DSH 后重试";
62
+ readonly shareErrSessionNotFound: "会话不存在或已被删除";
63
+ readonly shareErrBadRequest: "分享请求无效,请重试";
64
+ readonly shareErrNetwork: "网络连接失败,请检查网络后重试";
65
+ readonly shareErrServer: "服务暂时不可用,请稍后重试";
66
+ readonly shareErrBody: "分享数据异常,请重试";
67
+ readonly shareErrProbe: "当前浏览器无法生成分享图";
68
+ readonly shareErrRender: "生成分享图失败,请重试";
69
+ readonly shareErrSliceBudget: "分享图单段内容超出画布上限,请缩小分享范围后重试";
70
+ readonly shareErrEmpty: "会话还没有可分享的内容";
71
+ readonly shareStitchUnsupported: "当前浏览器暂不支持合成单张长图,已导出为多张图片";
72
+ readonly shareStitchFailed: "合成单张长图失败,已改为多张图片导出";
50
73
  readonly settings: "设置";
51
74
  readonly chipTaskboard: "任务看板";
52
75
  readonly chipSsh: "SSH";
@@ -1 +1 @@
1
- {"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,eAAO,MAAM,EAAE,cAAc,CAAA;AAE7B,mEAAmE;AACnE,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmDL,CAAA;AAEV,wEAAwE;AACxE,eAAO,MAAM,EAAE,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAmD3C,CAAA;AAED,2EAA2E;AAC3E,MAAM,MAAM,YAAY,GAAG,MAAM,OAAO,EAAE,CAAA"}
1
+ {"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,eAAO,MAAM,EAAE,cAAc,CAAA;AAE7B,mEAAmE;AACnE,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0EL,CAAA;AAEV,wEAAwE;AACxE,eAAO,MAAM,EAAE,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CA0E3C,CAAA;AAED,2EAA2E;AAC3E,MAAM,MAAM,YAAY,GAAG,MAAM,OAAO,EAAE,CAAA"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Share-export fetching (ticket 05, PLAN §5.3): the ONE typed fetch over
3
+ * GET /_dsh/mobile-nav/share-export, shared by the real share flow
4
+ * (MobileSessionInfo) and the debug preview (?mobile-nav-share-preview=1)
5
+ * so query assembly and error handling never fork into two copies.
6
+ *
7
+ * Module-shape rules (same reason as rasterize.ts): the share-image check
8
+ * script imports this file through Node type stripping, which rejects JSX
9
+ * and runs no browser API — so every import of a JSX-bearing module is
10
+ * `import type` (fully erased) and browser globals appear only inside
11
+ * function bodies. The query builder and the error taxonomy below are pure
12
+ * and pinned there.
13
+ */
14
+ import type { ShareTurn } from './share-card.tsx';
15
+ /** Route the host registers (mirror of src/share-export.ts — the client cannot import host code). */
16
+ export declare const SHARE_EXPORT_ROUTE = "/_dsh/mobile-nav/share-export";
17
+ /**
18
+ * Which slice of the transcript to export — exactly the `range`/`turns` pair
19
+ * the route validates (src/share-export.ts: `turns` next to `range=all` is a
20
+ * 400, so it is only ever appended for `range=last`).
21
+ */
22
+ export type ShareRange = {
23
+ kind: 'all';
24
+ } | {
25
+ kind: 'last';
26
+ turns: number;
27
+ };
28
+ /** Preset turn counts the range sheet offers (PLAN §6: presets, no free input). */
29
+ export declare const SHARE_TURNS_PRESETS: readonly number[];
30
+ /**
31
+ * Machine-readable fetch failure. `code` picks the localized copy; `status`
32
+ * is the HTTP status when a response existed (undefined = the request never
33
+ * completed). Fields are assigned in the constructor body, not declared as
34
+ * parameter properties — Node type stripping rejects that syntax (same
35
+ * reason as ShareRasterizeError / ShareExportError).
36
+ */
37
+ export type ShareFetchErrorCode =
38
+ /** 404 without the route's JSON envelope: this DSH never mounted the route (too old). */
39
+ 'route-missing'
40
+ /** 404 + the route's own session-not-found code: the session is gone. */
41
+ | 'session-not-found'
42
+ /** 400: malformed query the client should not have built. */
43
+ | 'bad-request'
44
+ /** Any other non-ok status (route error, gateway 502, …). */
45
+ | 'server'
46
+ /** fetch() itself threw (offline, connection reset). */
47
+ | 'network'
48
+ /** 2xx but the body is not the agreed shape. */
49
+ | 'bad-body';
50
+ /** Localizable fetch failure; ticket 05 maps `code` to locales shareErr*. */
51
+ export declare class ShareFetchError extends Error {
52
+ readonly code: ShareFetchErrorCode;
53
+ readonly status: number | undefined;
54
+ constructor(code: ShareFetchErrorCode, status: number | undefined, detail: string);
55
+ }
56
+ /** A validated export payload (the route's 200 body minus the envelope). */
57
+ export interface ShareExportData {
58
+ turns: ShareTurn[];
59
+ createdAt: number | undefined;
60
+ }
61
+ /**
62
+ * Pure: assemble the route query. The session id arrives in its branded
63
+ * `session-<uuid>` form (useSessions row / session-scoped slot prop) and is
64
+ * passed through encodeURIComponent — a bare uuid would 404 (PLAN §2 note).
65
+ */
66
+ export declare function buildShareExportQuery(session: string, range: ShareRange): string;
67
+ /**
68
+ * Fetch one export and classify every failure.
69
+ *
70
+ * Error mapping (the reason a 404 is split in two): this plugin's route
71
+ * answers 404 + `{error:{code:'session-not-found'}}` for an unknown session,
72
+ * while a DSH old enough to lack the route answers the web server's own 404
73
+ * — a different copy (“需要新版 DSH”) than “会话不存在”, decided by whether
74
+ * our envelope is present.
75
+ */
76
+ export declare function fetchShareExport(session: string, range: ShareRange): Promise<ShareExportData>;
77
+ //# sourceMappingURL=fetch-share.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch-share.d.ts","sourceRoot":"","sources":["../../../../src/client/share/fetch-share.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAEjD,qGAAqG;AACrG,eAAO,MAAM,kBAAkB,kCAAkC,CAAA;AAEjE;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG;IAAE,IAAI,EAAE,KAAK,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAE1E,mFAAmF;AACnF,eAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,EAAe,CAAA;AAEhE;;;;;;GAMG;AACH,MAAM,MAAM,mBAAmB;AAC7B,yFAAyF;AACvF,eAAe;AACjB,yEAAyE;GACvE,mBAAmB;AACrB,6DAA6D;GAC3D,aAAa;AACf,6DAA6D;GAC3D,QAAQ;AACV,wDAAwD;GACtD,SAAS;AACX,gDAAgD;GAC9C,UAAU,CAAA;AAEd,6EAA6E;AAC7E,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAA;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;gBAEvB,IAAI,EAAE,mBAAmB,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM;CAMlF;AAED,4EAA4E;AAC5E,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,SAAS,EAAE,CAAA;IAClB,SAAS,EAAE,MAAM,GAAG,SAAS,CAAA;CAC9B;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,MAAM,CAIhF;AAyBD;;;;;;;;GAQG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,eAAe,CAAC,CA8CnG"}
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Pure lenient Markdown-subset parser for the share card's assistant text
3
+ * (ticket 09, PLAN §5.1 v1.2).
4
+ *
5
+ * Scope = the constructs assistant transcripts actually use: ATX headings,
6
+ * paragraphs, ```/~~~ fenced code (with the language tag), 4-space indented
7
+ * code, blockquotes, ordered/unordered lists (two indent levels, 2-space
8
+ * multiples), GFM pipe tables with an alignment row, thematic breaks, and
9
+ * own-line image placeholders; inline bold/italic/strikethrough/code/link with
10
+ * backslash escapes. Nothing else — no HTML passthrough, no reference links,
11
+ * no setext headings, no syntax highlighting (PLAN §8 scope discipline).
12
+ *
13
+ * Leniency beats strictness ON PURPOSE (PLAN §5.1): a share image that shows
14
+ * stray asterisks is worse than one that shows plain text. Every marker pair
15
+ * must CLOSE before it counts — unclosed, empty or implausible markers fall
16
+ * back to their literal characters, so arbitrary prose round-trips as itself.
17
+ * The parser is also total: any input yields a tree (empty input yields []).
18
+ * Totality is enforced, not assumed: recursion (nested quotes, emphasis inside
19
+ * link text) carries a depth counter and a pathological 5000-deep "> > > …"
20
+ * degrades to literal paragraphs instead of overflowing the stack; one inline
21
+ * run longer than {@link INLINE_PLAIN_MAX} skips inline parsing entirely —
22
+ * every close-scan is then bounded, so no amount of unclosed markers can
23
+ * regress past linear time.
24
+ *
25
+ * Hard rule for this file: no imports, no JSX, no DOM, no module side effects.
26
+ * scripts/check-share-image.mjs loads it through Node type stripping, which
27
+ * EXECUTES every value import, and the client bundler inlines it next to
28
+ * share-card without new externals.
29
+ */
30
+ /** One inline run. Emphasis nests by recursion; code/link leaves are atomic. */
31
+ export type MdSpan = {
32
+ kind: 'text';
33
+ text: string;
34
+ } | {
35
+ kind: 'bold';
36
+ spans: MdSpan[];
37
+ } | {
38
+ kind: 'italic';
39
+ spans: MdSpan[];
40
+ } | {
41
+ kind: 'strike';
42
+ spans: MdSpan[];
43
+ } | {
44
+ kind: 'code';
45
+ text: string;
46
+ } | {
47
+ kind: 'link';
48
+ spans: MdSpan[];
49
+ href: string;
50
+ };
51
+ /** One list item: inline content plus one nested level (the parser's cap). */
52
+ export interface MdListItem {
53
+ spans: MdSpan[];
54
+ /** Ordered item number as written (undefined for bullets). */
55
+ number: number | undefined;
56
+ children: MdListItem[];
57
+ }
58
+ /** Column alignment from the delimiter row (null = unspecified/left). */
59
+ export type MdAlign = 'left' | 'center' | 'right' | null;
60
+ /** One recognized block. The renderer switches on `kind` only. */
61
+ export type MdBlock = {
62
+ kind: 'heading';
63
+ level: number;
64
+ spans: MdSpan[];
65
+ } | {
66
+ kind: 'para';
67
+ spans: MdSpan[];
68
+ } | {
69
+ kind: 'code';
70
+ lang: string | undefined;
71
+ lines: string[];
72
+ } | {
73
+ kind: 'quote';
74
+ children: MdBlock[];
75
+ } | {
76
+ kind: 'list';
77
+ ordered: boolean;
78
+ items: MdListItem[];
79
+ } | {
80
+ kind: 'table';
81
+ align: MdAlign[];
82
+ header: MdSpan[][];
83
+ rows: MdSpan[][][];
84
+ } | {
85
+ kind: 'hr';
86
+ } | {
87
+ kind: 'image';
88
+ alt: string;
89
+ src: string;
90
+ };
91
+ /**
92
+ * Parse a raw text block into the share card's Markdown tree.
93
+ * @param text - raw text-block content from the share-export route.
94
+ * @returns blocks in reading order; `[]` for blank input.
95
+ */
96
+ export declare function parseMarkdown(text: string): MdBlock[];
97
+ //# sourceMappingURL=markdown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../../../src/client/share/markdown.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAIH,gFAAgF;AAChF,MAAM,MAAM,MAAM,GACd;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,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,GACnC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,GACnC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAInD,8EAA8E;AAC9E,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,8DAA8D;IAC9D,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1B,QAAQ,EAAE,UAAU,EAAE,CAAA;CACvB;AAED,yEAAyE;AACzE,MAAM,MAAM,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,IAAI,CAAA;AAExD,kEAAkE;AAClE,MAAM,MAAM,OAAO,GACf;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,GACnD;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,GACjC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,GAC3D;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,OAAO,EAAE,CAAA;CAAE,GACtC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,UAAU,EAAE,CAAA;CAAE,GACvD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAA;CAAE,GAC3E;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,GACd;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAA;AA0hB/C;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CAErD"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Streaming PNG long-image stitcher (ticket 07, PLAN §4.5).
3
+ *
4
+ * The rasterizer still paints slice-by-slice (each slice stays under the iOS
5
+ * canvas budget), but the DELIVERABLE is one PNG: this module concatenates the
6
+ * slices' scanlines into a single image — PNG signature + IHDR (whole-image
7
+ * size, 8-bit RGBA) + IDAT (zlib stream, scanline filter 0) + IEND, CRC32
8
+ * computed against a self-carried table, compression via the platform's
9
+ * `CompressionStream('deflate')` whose output already IS the zlib-wrapped
10
+ * deflate stream the PNG IDAT chunk requires.
11
+ *
12
+ * Streaming discipline (the whole point of a custom stitcher): slices are
13
+ * decoded one at a time (createImageBitmap → offscreen canvas getImageData →
14
+ * per-row writeRow → release) so the raw pixels of the WHOLE image are never
15
+ * held; only the compressed output accumulates, as a Blob.
16
+ *
17
+ * Module contract (same one share-flow.ts honors): scripts/check-share-image.mjs
18
+ * imports this file through Node type stripping, which EXECUTES module scope —
19
+ * so everything here is pure math at module scope, and every browser global
20
+ * (CompressionStream, createImageBitmap, document, Blob) is touched only
21
+ * inside function/class bodies. The file is an import-free leaf.
22
+ */
23
+ /**
24
+ * CRC-32 (PNG variant: no init/xor toggling beyond the standard reflected
25
+ * form — init 0xFFFFFFFF, final XOR 0xFFFFFFFF). Known answers pinned by the
26
+ * check script: crc32('123456789') === 0xCBF43926, crc32('IEND') === 0xAE426082.
27
+ */
28
+ export declare function crc32(bytes: Uint8Array): number;
29
+ /** The 8-byte PNG file signature. */
30
+ export declare const PNG_SIGNATURE: Uint8Array<ArrayBuffer>;
31
+ /**
32
+ * One PNG chunk frame: 4-byte big-endian data length, the type, the data, and
33
+ * the big-endian CRC32 over TYPE+DATA (never the length field).
34
+ */
35
+ export declare function pngChunk(type: string, data: Uint8Array): Uint8Array<ArrayBuffer>;
36
+ /** IHDR payload: width/height big-endian, 8 bits/channel, RGBA (color type 6), deflate, filter 0, no interlace. */
37
+ export declare function ihdrData(width: number, height: number): Uint8Array<ArrayBuffer>;
38
+ /** The IEND chunk is a constant: empty payload, CRC 0xAE426082. */
39
+ export declare const IEND_CHUNK: Uint8Array<ArrayBuffer>;
40
+ /**
41
+ * IDAT payload split ceiling (1 MiB): a single IDAT may legally be any size,
42
+ * but decoders appreciate modest chunks — and splitting keeps the multi-IDAT
43
+ * path honest since most exports stay under it and emit exactly one.
44
+ */
45
+ export declare const MAX_IDAT_CHUNK_BYTES: number;
46
+ /** Split compressed bytes into consecutive IDAT payloads (multi-chunk IDAT is legal and order-significant). */
47
+ export declare function splitIdatBytes(data: Uint8Array<ArrayBuffer>, maxBytes: number): Uint8Array<ArrayBuffer>[];
48
+ /**
49
+ * Row-by-row PNG assembler for one already-known raster size.
50
+ *
51
+ * Backpressure: the compressed readable side is drained by a loop started in
52
+ * the constructor — without a concurrent reader the transform stalls once its
53
+ * buffers fill and every writeRow would deadlock. Row bytes are NOT copied:
54
+ * writeRow's awaited write resolves only after the compressor has consumed the
55
+ * chunk, and the rasterizer feeds disjoint subarrays of an ImageData buffer
56
+ * that is never mutated after getImageData, so no staging buffer is needed.
57
+ */
58
+ export declare class PngStitchWriter {
59
+ readonly width: number;
60
+ readonly height: number;
61
+ private rowsWritten;
62
+ private closed;
63
+ private readonly writer;
64
+ private readonly drained;
65
+ constructor(width: number, height: number);
66
+ /** Feed one scanline (`width × 4` RGBA bytes). The bytes must stay untouched until the returned promise resolves. */
67
+ writeRow(rgba: Uint8Array<ArrayBuffer>): Promise<void>;
68
+ /** Close the stream and assemble the file. Rejects unless exactly `height` rows were fed. */
69
+ finish(): Promise<Blob>;
70
+ }
71
+ /**
72
+ * Feature probe for the stitch path (ticket 07): CompressionStream gates the
73
+ * whole mechanism (Safari gained it in 16.4, alongside createImageBitmap and
74
+ * Blob — the trio is checked anyway because each is independently load-bearing).
75
+ * Unavailable → the caller falls back to the legacy multi-file delivery.
76
+ */
77
+ export declare function supportsPngStitch(): boolean;
78
+ /** One stitched slice: its painted PNG and its physical height. */
79
+ export interface StitchSlicePart {
80
+ blob: Blob;
81
+ /** Physical px height of the slice (the count of scanlines it contributes). */
82
+ height: number;
83
+ }
84
+ /**
85
+ * Stitch painted slices into one PNG of the shared physical width.
86
+ *
87
+ * Per slice: createImageBitmap → draw onto a throwaway canvas → getImageData →
88
+ * writeRow per scanline → zero the canvas and close the bitmap before the next
89
+ * slice. getImageData un-premultiplies alpha, which is lossy for translucent
90
+ * pixels — the share card paints on an opaque background (alpha 255), so the
91
+ * round-trip is exact here. Every slice must decode at exactly the global
92
+ * width and its declared height; a mismatch means a painting bug, and the
93
+ * writer's row accounting would corrupt the file, so it fails loudly instead.
94
+ */
95
+ export declare function stitchSliceBlobs(parts: readonly StitchSlicePart[], width: number): Promise<Blob>;
96
+ //# sourceMappingURL=png-stitch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"png-stitch.d.ts","sourceRoot":"","sources":["../../../../src/client/share/png-stitch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAiBH;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAM/C;AAED,qCAAqC;AACrC,eAAO,MAAM,aAAa,EAAE,UAAU,CAAC,WAAW,CAAoE,CAAA;AAgBtH;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,UAAU,CAAC,WAAW,CAAC,CAYhF;AAED,mHAAmH;AACnH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,UAAU,CAAC,WAAW,CAAC,CAU/E;AAED,mEAAmE;AACnE,eAAO,MAAM,UAAU,EAAE,UAAU,CAAC,WAAW,CAAuC,CAAA;AAEtF;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,QAAU,CAAA;AAE3C,+GAA+G;AAC/G,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,CAAC,WAAW,CAAC,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU,CAAC,WAAW,CAAC,EAAE,CAQzG;AAUD;;;;;;;;;GASG;AACH,qBAAa,eAAe;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,OAAO,CAAC,WAAW,CAAI;IACvB,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA2C;IAClE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkC;gBAE9C,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAiCzC,qHAAqH;IAC/G,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC,WAAW,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAW5D,6FAA6F;IACvF,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;CAe9B;AAOD;;;;;GAKG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAED,mEAAmE;AACnE,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,IAAI,CAAA;IACV,+EAA+E;IAC/E,MAAM,EAAE,MAAM,CAAA;CACf;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,SAAS,eAAe,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAsCtG"}
@@ -0,0 +1,317 @@
1
+ /**
2
+ * Share-card slicing + rasterization (ticket 04, PLAN §5.2).
3
+ *
4
+ * Turns a rendered ShareCard DOM tree (ticket 03: fully inline-styled, marked
5
+ * with data-share-card / -header / -turn / -role / -footer) into a sequence of
6
+ * PNG blobs: slice by measured turn heights under a per-slice canvas pixel
7
+ * budget, then paint each slice through
8
+ * `<svg><foreignObject>` → Image → canvas → PNG export. The SVG document
9
+ * carries each slice's PHYSICAL target size (intrinsic = canvas), so the image
10
+ * rasterizes sharp at the canvas's own resolution and the draw is a 1:1 copy.
11
+ *
12
+ * File layout, and why it matters:
13
+ * - The top half is PURE planning math (no DOM, no React). It may import
14
+ * ONLY the pure constants module of this directory (share-constants.ts):
15
+ * scripts/check-share-image.mjs imports this file through Node type
16
+ * stripping, which EXECUTES every value import — JSX or DOM code would
17
+ * break the check, and any other module would widen the client bundle.
18
+ * - The DOM half below only MEASURES and EXECUTES the pure plan's decisions:
19
+ * slice grouping, width lifting, scale factors, pixel-ratio fallback and
20
+ * truncation are all decided by the pure functions, and the SVG document the
21
+ * painter draws is built by a pure serializer — so tests can pin all of it.
22
+ *
23
+ * Release policy per slice (PLAN §5.2): each slice is painted, blobbed and its
24
+ * canvas dropped before the next slice is built — the export never holds more
25
+ * than one live canvas. Ticket 07 adds the `unified` planning mode whose
26
+ * single physical width lets png-stitch.ts concatenate the slice PNGs into
27
+ * one long image.
28
+ */
29
+ /** Per-slice physical pixel budget (iOS canvas hard cap 16.7M minus margin). */
30
+ export declare const SLICE_PIXEL_BUDGET = 12000000;
31
+ /** Slice count ceiling; beyond it the head is dropped and the first slice marked. */
32
+ export declare const MAX_SLICES = 24;
33
+ /** Pixel-ratio fallback ladder, walked top-down from min(devicePixelRatio, 2). */
34
+ export declare const PIXEL_RATIO_STEPS: readonly number[];
35
+ /** Safety px added around computed ultra widths (sub-pixel + rounding slack). */
36
+ export declare const ULTRA_WIDTH_MARGIN = 2;
37
+ /**
38
+ * Machine-readable failure kinds; ticket 05 maps them to localized copy.
39
+ * `slice-over-budget` (review 07+08): a unified-mode slice whose freshly
40
+ * MEASURED height busts the per-slice canvas budget at the global stitch
41
+ * width — the planner's global lever should have prevented it, and the old
42
+ * behavior (floor the height, silently clip the bottom) was invisible data
43
+ * loss, so it fails loudly instead.
44
+ */
45
+ export type ShareRasterizeErrorCode = 'probe-failed' | 'image-load' | 'canvas' | 'empty-card' | 'slice-over-budget';
46
+ /** Localizable rasterize failure. `code` drives UI copy, `detail` is diagnostic. */
47
+ export declare class ShareRasterizeError extends Error {
48
+ readonly code: ShareRasterizeErrorCode;
49
+ readonly detail: string;
50
+ constructor(code: ShareRasterizeErrorCode, detail: string);
51
+ }
52
+ /** Descending pixel-ratio candidates for an already-clamped cap; never empty. */
53
+ export declare function ratioChain(cap: number): number[];
54
+ /** Physical pixels a slice occupies once painted (rounding up, canvas-style). */
55
+ export declare function slicePixelCount(outWidth: number, outHeight: number, pixelRatio: number): number;
56
+ /** Physical raster geometry of one slice: svg/foreignObject/canvas all take these. */
57
+ export interface RasterGeometry {
58
+ /** Physical px width (>= 1). */
59
+ width: number;
60
+ /** Physical px height (>= 1). */
61
+ height: number;
62
+ /** Wrapper transform = scale × pixelRatio (further shrunk when clamped). */
63
+ transformScale: number;
64
+ }
65
+ /**
66
+ * Physical target size for one slice's raster. The svg/foreignObject
67
+ * width/height attributes MUST be these numbers, never the logical ones: the
68
+ * browser rasterizes an SVG-as-image at its INTRINSIC size, so logical
69
+ * attributes make WebKit decode a small bitmap that drawImage then upscales
70
+ * (blurry on 2x screens) — and an unbounded ultra renderWidth would ask the
71
+ * decoder for an unbounded bitmap, sailing past the pixel budget before any
72
+ * canvas is allocated. The logical tree instead rides an inner wrapper scaled
73
+ * by `transformScale`, so the canvas draw is a 1:1 copy. Sizes are clamped
74
+ * into `budgetPx` (floor per side when clamping, so the product provably fits
75
+ * even where the unclamped per-side ceil would not have).
76
+ */
77
+ export declare function rasterGeometry(renderWidth: number, logicalHeight: number, scale: number, pixelRatio: number, budgetPx: number): RasterGeometry;
78
+ /** Inputs of {@link buildSvgDocument}: logical tree size + physical target. */
79
+ export interface SvgDocumentSpec {
80
+ /** Logical px width of the slice tree (the wrapper's width). */
81
+ renderWidth: number;
82
+ /** Logical px height of the slice tree (the wrapper's height). */
83
+ logicalHeight: number;
84
+ /** Physical target width ({@link RasterGeometry.width}). */
85
+ width: number;
86
+ /** Physical target height ({@link RasterGeometry.height}). */
87
+ height: number;
88
+ /** Wrapper transform ({@link RasterGeometry.transformScale}). */
89
+ transformScale: number;
90
+ }
91
+ /**
92
+ * Serialize one slice subtree into a standalone SVG document sized at its
93
+ * PHYSICAL target: svg + foreignObject carry the physical px, the xhtml
94
+ * wrapper carries the logical size and a scale() transform, so the image
95
+ * rasterizes at exactly the resolution the canvas wants (no upscaled copy).
96
+ * `bodyHtml` is the prepared slice clone's outerHTML — namespace attributes
97
+ * must already be baked in by the caller (prepareXmlSerialization).
98
+ */
99
+ export declare function buildSvgDocument(bodyHtml: string, spec: SvgDocumentSpec): string;
100
+ /**
101
+ * Card width that fits `contentWidth` of assistant-style rows: the row spans
102
+ * the card's whole content box, so only the card padding stacks on top.
103
+ */
104
+ export declare function cardWidthForContent(contentWidth: number, cardPaddingX: number): number;
105
+ /**
106
+ * Card width that fits `contentWidth` inside a user bubble: the bubble is
107
+ * capped at {@link USER_BUBBLE_MAX_RATIO} of the row and adds its own padding,
108
+ * so the percentage constraint inverts into a division.
109
+ */
110
+ export declare function cardWidthForUserBubble(contentWidth: number, cardPaddingX: number): number;
111
+ /** Card width a turn's measured content needs, by turn role. */
112
+ export declare function requiredCardWidth(role: 'user' | 'assistant', contentWidth: number, cardPaddingX: number): number;
113
+ /** Which slice geometry a turn needs, from its measured natural content width. */
114
+ export type TurnWidthBucket = 'base' | 'wide' | 'ultra';
115
+ /** Bucket one turn: base width / widened (~780) / wider-than-wide (scale-to-fit). */
116
+ export declare function bucketTurnWidth(role: 'user' | 'assistant', contentWidth: number, cardPaddingX: number, baseWidth: number, wideWidth: number): TurnWidthBucket;
117
+ /**
118
+ * Scale-to-fit geometry for content whose needed card width exceeds the wide
119
+ * width: render the slice at full natural width, shrink geometrically on
120
+ * output (pinch-zoom in the image viewer recovers readability — PLAN §5.2).
121
+ */
122
+ export declare function ultraSliceWidth(role: 'user' | 'assistant', contentWidth: number, cardPaddingX: number, wideWidth: number): {
123
+ renderWidth: number;
124
+ scale: number;
125
+ };
126
+ /** One turn's final geometry as the DOM measured it at {@link renderWidth}. */
127
+ export interface PlannedTurnInput {
128
+ /** Logical px height of the turn at {@link renderWidth}. */
129
+ height: number;
130
+ /** Logical px width the turn's slice renders at (base / wide / ultra). */
131
+ renderWidth: number;
132
+ /** Geometric output downscale (1 except ultra turns). */
133
+ scale: number;
134
+ }
135
+ /** Fixed card chrome the planner must reserve height for. */
136
+ export interface PlanExtras {
137
+ /** Vertical gap between the card root's column children. */
138
+ gap: number;
139
+ /** Card root top + bottom padding. */
140
+ paddingY: number;
141
+ /** Header strip height (reserved on the first slice). */
142
+ headerHeight: number;
143
+ /** Footer strip height (reserved on the last slice). */
144
+ footerHeight: number;
145
+ /** Truncation-note bar height (reserved on a truncated last slice). */
146
+ noteHeight: number;
147
+ }
148
+ export interface PlanOptions {
149
+ budgetPx: number;
150
+ maxSlices: number;
151
+ /** Descending pixel-ratio candidates (see {@link ratioChain}). */
152
+ ratios: readonly number[];
153
+ }
154
+ /** One planned output image. */
155
+ export interface PlannedSlice {
156
+ /** Inclusive index of the first turn in this slice. */
157
+ from: number;
158
+ /** Inclusive index of the last turn in this slice. */
159
+ to: number;
160
+ /** Logical width the slice must be cloned/rendered at. */
161
+ renderWidth: number;
162
+ /** Total geometric downscale (ultra fit × budget lever; 1 = native). */
163
+ scale: number;
164
+ /** Logical height at {@link renderWidth}, chrome included. */
165
+ height: number;
166
+ /** Output logical width (renderWidth × scale). */
167
+ outWidth: number;
168
+ /** Output logical height (height × scale). */
169
+ outHeight: number;
170
+ /** Extra shrink applied to satisfy the pixel budget (1 = none). */
171
+ lever: number;
172
+ /** Carries the header strip (and the note when {@link truncated}). */
173
+ first: boolean;
174
+ /** Carries the footer strip. */
175
+ last: boolean;
176
+ /** True only on the first slice of a truncated export (marks the dropped head). */
177
+ truncated: boolean;
178
+ }
179
+ export interface SlicePlan {
180
+ pixelRatio: number;
181
+ slices: PlannedSlice[];
182
+ truncated: boolean;
183
+ /** Turns dropped from the HEAD by the {@link PlanOptions.maxSlices} ceiling (the tail — the recent turns a share is about — is always kept). */
184
+ droppedTurns: number;
185
+ }
186
+ /**
187
+ * Plan the slice sequence (legacy multi-file geometry, ticket 04). Beyond the
188
+ * shared walk, any slice still over budget (an un-splittable giant turn, or
189
+ * the header/footer chrome nudging a full slice past the line) is shrunk
190
+ * uniformly PER SLICE (the `lever`) — geometric scaling keeps completeness,
191
+ * zoom restores size. Per-slice levers make physical widths diverge, which is
192
+ * fine for separate files and exactly what the stitched planner removes.
193
+ *
194
+ * Deterministic pure function of its inputs.
195
+ */
196
+ export declare function planShareSlices(turns: readonly PlannedTurnInput[], extras: PlanExtras, opts: PlanOptions): SlicePlan;
197
+ /**
198
+ * Global logical width of a stitched export: 390 unless ANY turn needs wide
199
+ * content, then 780 for the WHOLE card — one long image must not mix widths
200
+ * (scanlines concatenate only when every slice has the same pixel width; a
201
+ * mid-image line-length jump would read as a glitch anyway).
202
+ */
203
+ export declare function unifiedCardWidth(anyTurnNeedsWide: boolean): number;
204
+ /**
205
+ * Plan the slice sequence for a stitched single-PNG export. Same walk and
206
+ * truncation semantics as {@link planShareSlices}, but the budget lever is
207
+ * GLOBAL: one multiplier shared by every slice, so every slice's output width
208
+ * is identical and the PNG stitcher can concatenate scanlines directly. The
209
+ * inputs must already be normalized to the global width by the caller (the
210
+ * DOM half measures the card once at {@link unifiedCardWidth}; turns still
211
+ * wider than that keep their per-turn ultra scale-to-fit, which lands on the
212
+ * same output width by construction).
213
+ */
214
+ export declare function planShareSlicesUnified(turns: readonly PlannedTurnInput[], extras: PlanExtras, opts: PlanOptions): SlicePlan;
215
+ /**
216
+ * Authoritative physical width of a stitched plan — the IHDR width of the
217
+ * single long image. Every slice's own `outWidth × pixelRatio` must already
218
+ * agree; the max absorbs the ±ε float noise of ultra scales
219
+ * (renderWidth × W/renderWidth), so one integer serves the whole card and the
220
+ * painter FORCES it (rasterGeometryStitch) instead of re-deriving it per slice.
221
+ */
222
+ export declare function stitchPixelWidth(plan: SlicePlan): number;
223
+ /** Stitch-mode raster geometry: the width is the GLOBAL one, never this slice's own arithmetic. */
224
+ export interface StitchRasterGeometry {
225
+ /** Physical px width — always the caller's global width, byte-for-byte. */
226
+ width: number;
227
+ /** Physical px height (>= 1); when `clamped`, the floor'd budget-capped height. */
228
+ height: number;
229
+ /** Wrapper transform = globalWidth / renderWidth (ultra fit × lever × ratio all folded in). */
230
+ transformScale: number;
231
+ /**
232
+ * True when the measured height busts the per-slice budget at the global
233
+ * width (the global lever should have prevented this). The pure function
234
+ * still returns the floor'd geometry so the check script can pin the
235
+ * arithmetic — the DOM half REFUSES to paint a clamped slice (a floor'd
236
+ * height means the bottom of the card is silently cut off: visible data
237
+ * loss, review 07+08 plan B).
238
+ */
239
+ clamped: boolean;
240
+ }
241
+ /**
242
+ * Physical raster geometry of one slice in stitch mode. The width is pinned to
243
+ * `globalWidth` so every slice's canvas — and the scanlines the stitcher reads
244
+ * from it — is exactly as wide as the final image; a slice that freshly
245
+ * measures past the per-slice budget would need a height CLIP (never a width
246
+ * shrink: that would break concatenation, never aspect distortion), and the
247
+ * painter rejects that case instead of clipping (see `clamped`). The
248
+ * planner's global lever is the mechanism that keeps slices in budget; this
249
+ * clamp is the loud tripwire behind it.
250
+ */
251
+ export declare function rasterGeometryStitch(renderWidth: number, logicalHeight: number, globalWidth: number, budgetPx: number): StitchRasterGeometry;
252
+ export interface RasterizeOptions {
253
+ /** Upper bound for the pixel ratio; default min(devicePixelRatio, 2). */
254
+ pixelRatioCap?: number;
255
+ /** Truncation-note copy, fed the count of dropped head turns (ticket 05 localizes it). */
256
+ truncatedNote?: (droppedTurns: number) => string;
257
+ /** Called 0/N once and done/N after each slice. */
258
+ onProgress?: (done: number, total: number) => void;
259
+ /**
260
+ * Stitch mode (ticket 07, PLAN §4.5): plan for a single-PNG stitched export —
261
+ * one global logical width (390/780), one global pixel ratio and one global
262
+ * budget lever, so every slice shares the same physical width and
263
+ * png-stitch.ts can concatenate scanlines. Default false keeps the legacy
264
+ * per-slice-width multi-file geometry (the fallback path for browsers
265
+ * without CompressionStream).
266
+ */
267
+ unified?: boolean;
268
+ }
269
+ export interface RasterizeResult {
270
+ pixelRatio: number;
271
+ truncated: boolean;
272
+ droppedTurns: number;
273
+ slices: RasterizedSlice[];
274
+ /**
275
+ * Unified mode only: the physical pixel width every slice shares — the IHDR
276
+ * width of the stitched long image (undefined in legacy mode).
277
+ */
278
+ stitchWidth: number | undefined;
279
+ }
280
+ export interface RasterizedSlice {
281
+ index: number;
282
+ blob: Blob;
283
+ /** Physical px size of the PNG. */
284
+ width: number;
285
+ height: number;
286
+ /** Logical slice width (before scaling). */
287
+ logicalWidth: number;
288
+ /** Total geometric scale applied on output. */
289
+ scale: number;
290
+ /** Transcript seq of the first/last turn in this slice (debug + ordering). */
291
+ fromSeq: number;
292
+ toSeq: number;
293
+ }
294
+ /**
295
+ * 2×2 feature probe (PLAN §5.2): before the first export, rasterize a tiny
296
+ * known card (four solid color cells through the exact foreignObject → Image
297
+ * → canvas path) and read the pixels back. Old Safari renders foreignObject
298
+ * images as nothing — exporting then would produce blank PNGs, so the probe
299
+ * fails loudly once per page load instead. Failure is sticky.
300
+ */
301
+ export declare function ensureProbe(): Promise<void>;
302
+ /**
303
+ * Rasterize a rendered share card into a PNG sequence.
304
+ *
305
+ * Pipeline: probe once → measure a clone at base width → bucket turns (pure)
306
+ * → measure wide/ultra turns at their final widths → plan slices (pure) →
307
+ * per slice: prune a clone to the slice, measure, serialize into SVG, paint,
308
+ * release. The returned blobs are owned by the caller (object URLs etc.).
309
+ *
310
+ * `unified` (ticket 07) flips both planning decisions the stitch depends on:
311
+ * one global logical width (re-measured once at 390 or 780 — a card that
312
+ * lifts ANY turn to wide renders every turn wide) and one global budget
313
+ * lever, so `stitchWidth` comes back as the single physical width every slice
314
+ * shares and png-stitch.ts can concatenate the scanlines into one PNG.
315
+ */
316
+ export declare function rasterizeShareCard(card: HTMLElement, options?: RasterizeOptions): Promise<RasterizeResult>;
317
+ //# sourceMappingURL=rasterize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rasterize.d.ts","sourceRoot":"","sources":["../../../../src/client/share/rasterize.ts"],"names":[],"mappings":"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"}