@khorsheed/dsh-reader 0.3.0 → 0.3.1

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 (87) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.en.md +26 -121
  3. package/README.i18n.yaml +3 -3
  4. package/README.md +26 -121
  5. package/cordis.patch.yml +7 -0
  6. package/lib/client.js +29054 -6698
  7. package/lib/index.js +3416 -334
  8. package/lib/typert.host.js +2145 -259
  9. package/lib/typert.remote-client.d.ts +97 -3
  10. package/lib/typert.remote-client.js +1590 -250
  11. package/lib/types/captions.d.ts +99 -0
  12. package/lib/types/captions.js +236 -0
  13. package/lib/types/client/MediaPlayer.d.ts +55 -0
  14. package/lib/types/client/MediaPlayer.js +106 -0
  15. package/lib/types/client/NotesView.d.ts +57 -0
  16. package/lib/types/client/NotesView.js +124 -0
  17. package/lib/types/client/ReaderPane.d.ts +20 -3
  18. package/lib/types/client/ReaderPane.js +860 -164
  19. package/lib/types/client/SpeakerLegend.d.ts +18 -0
  20. package/lib/types/client/SpeakerLegend.js +26 -0
  21. package/lib/types/client/canvas-sync.d.ts +84 -0
  22. package/lib/types/client/canvas-sync.js +59 -0
  23. package/lib/types/client/contract.d.ts +130 -3
  24. package/lib/types/client/extract-article.d.ts +38 -1
  25. package/lib/types/client/extract-article.js +501 -28
  26. package/lib/types/client/icons.d.ts +1 -1
  27. package/lib/types/client/icons.js +1 -1
  28. package/lib/types/client/index.js +81 -2
  29. package/lib/types/client/locales.d.ts +279 -11
  30. package/lib/types/client/locales.js +278 -10
  31. package/lib/types/client/notes.d.ts +142 -0
  32. package/lib/types/client/notes.js +334 -0
  33. package/lib/types/client/parse-rss.js +40 -0
  34. package/lib/types/client/preset-visibility.d.ts +93 -0
  35. package/lib/types/client/preset-visibility.js +115 -0
  36. package/lib/types/client/selectors.d.ts +5 -0
  37. package/lib/types/client/selectors.js +5 -0
  38. package/lib/types/client/session.d.ts +21 -1
  39. package/lib/types/client/session.js +12 -0
  40. package/lib/types/client/store.js +4 -1
  41. package/lib/types/client/translate.d.ts +26 -1
  42. package/lib/types/client/translate.js +29 -12
  43. package/lib/types/client/useLocalTranscript.d.ts +43 -0
  44. package/lib/types/client/useLocalTranscript.js +129 -0
  45. package/lib/types/client/useMedia.d.ts +68 -0
  46. package/lib/types/client/useMedia.js +299 -0
  47. package/lib/types/client/useMediaStates.d.ts +9 -0
  48. package/lib/types/client/useMediaStates.js +43 -0
  49. package/lib/types/client/useNotes.d.ts +110 -0
  50. package/lib/types/client/useNotes.js +423 -0
  51. package/lib/types/client/usePolish.d.ts +47 -0
  52. package/lib/types/client/usePolish.js +162 -0
  53. package/lib/types/index.d.ts +6 -0
  54. package/lib/types/index.js +3 -0
  55. package/lib/types/local-transcribe.d.ts +171 -0
  56. package/lib/types/local-transcribe.js +473 -0
  57. package/lib/types/media-host.d.ts +91 -0
  58. package/lib/types/media-host.js +230 -0
  59. package/lib/types/media.d.ts +100 -0
  60. package/lib/types/media.js +207 -0
  61. package/lib/types/notes-types.d.ts +158 -0
  62. package/lib/types/notes-types.js +8 -0
  63. package/lib/types/notes.d.ts +213 -0
  64. package/lib/types/notes.js +718 -0
  65. package/lib/types/polish.d.ts +41 -0
  66. package/lib/types/polish.js +136 -0
  67. package/lib/types/remote.d.ts +118 -0
  68. package/lib/types/remote.js +151 -1
  69. package/lib/types/service.d.ts +71 -1
  70. package/lib/types/service.js +280 -59
  71. package/lib/types/skill.d.ts +20 -0
  72. package/lib/types/skill.js +78 -0
  73. package/lib/types/store.js +30 -8
  74. package/lib/types/tool.d.ts +23 -0
  75. package/lib/types/tool.js +40 -0
  76. package/lib/types/tools.d.ts +69 -0
  77. package/lib/types/tools.js +400 -0
  78. package/lib/types/types.d.ts +87 -0
  79. package/lib/types/types.js +66 -0
  80. package/lib/types/xiaoyuzhou.d.ts +43 -0
  81. package/lib/types/xiaoyuzhou.js +134 -0
  82. package/lib/types/ytdlp.d.ts +111 -0
  83. package/lib/types/ytdlp.js +269 -0
  84. package/package.json +29 -20
  85. package/lib/invariant.js +0 -38
  86. package/lib/types/invariant.d.ts +0 -29
  87. package/lib/types/invariant.js +0 -38
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The transcript's speakers, one chip each, above the tabs: the colour that
3
+ * marks their paragraphs and their name — click to rename (Enter keeps it,
4
+ * Esc drops it, an empty name goes back to 说话人 A). A rename applies to the
5
+ * whole transcript and is kept on the host.
6
+ *
7
+ * @module @khorsheed/dsh-reader/client/SpeakerLegend
8
+ */
9
+ import { type ReactNode } from 'react';
10
+ export interface SpeakerLegendProps {
11
+ readonly count: number;
12
+ readonly label: (speaker: number) => string;
13
+ readonly onRename: (speaker: number, name: string) => void;
14
+ readonly hint: string;
15
+ readonly renameTitle: string;
16
+ }
17
+ export declare function SpeakerLegend(props: SpeakerLegendProps): ReactNode;
18
+ //# sourceMappingURL=SpeakerLegend.d.ts.map
@@ -0,0 +1,26 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * The transcript's speakers, one chip each, above the tabs: the colour that
4
+ * marks their paragraphs and their name — click to rename (Enter keeps it,
5
+ * Esc drops it, an empty name goes back to 说话人 A). A rename applies to the
6
+ * whole transcript and is kept on the host.
7
+ *
8
+ * @module @khorsheed/dsh-reader/client/SpeakerLegend
9
+ */
10
+ import { useState } from 'react';
11
+ import css from './ReaderPane.module.css';
12
+ export function SpeakerLegend(props) {
13
+ const { count, label, onRename, hint, renameTitle } = props;
14
+ const [editing, setEditing] = useState(null);
15
+ const [draft, setDraft] = useState('');
16
+ return (_jsxs("div", { className: css.speakerLegend, children: [Array.from({ length: count }, (_, speaker) => (editing === speaker
17
+ ? (_jsx("input", { className: css.speakerInput, "data-spk": speaker, autoFocus: true, value: draft, "aria-label": renameTitle, onChange: event => { setDraft(event.target.value); }, onBlur: () => { setEditing(null); }, onKeyDown: event => {
18
+ if (event.key === 'Enter') {
19
+ onRename(speaker, draft);
20
+ setEditing(null);
21
+ }
22
+ if (event.key === 'Escape')
23
+ setEditing(null);
24
+ } }, speaker))
25
+ : (_jsx("button", { type: "button", className: css.speakerChip, "data-spk": speaker, title: renameTitle, onClick: () => { setDraft(label(speaker)); setEditing(speaker); }, children: label(speaker) }, speaker)))), _jsx("span", { className: css.speakerHint, children: hint })] }));
26
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * 「同步到画布」: a note (or a discussion note) becomes a kept card on a canvas
3
+ * the reader picks.
4
+ *
5
+ * The canvas is another package. Its client provides `ctx.canvasInbox`
6
+ * (`@khorsheed/dsh-canvas/client/inbox.ts`); this module mirrors that shape
7
+ * structurally instead of importing it, and the pane hides the gesture when
8
+ * the probe answers nothing. One direction only: the reader writes to the
9
+ * canvas, the canvas never reaches back.
10
+ *
11
+ * The card carries its provenance as text (the article, the ¶, a link to the
12
+ * original) because the canvas does not render a card's `source` field; the
13
+ * structured `source` rides along for the model.
14
+ *
15
+ * @module @khorsheed/dsh-reader/client/canvas-sync
16
+ */
17
+ import type { ReaderDiscussion, ReaderNote } from '../notes-types.ts';
18
+ /** One canvas a card can go to (the inbox's list row). */
19
+ export interface SyncCanvas {
20
+ readonly id: string;
21
+ readonly title: string;
22
+ readonly cardCount: number;
23
+ }
24
+ /** One category a card can be filed under. */
25
+ export interface SyncKind {
26
+ readonly id: string;
27
+ readonly label: string;
28
+ }
29
+ /** What putting a card answers. */
30
+ export type SyncOutcome = {
31
+ readonly ok: true;
32
+ readonly cardId: string;
33
+ } | {
34
+ readonly ok: false;
35
+ readonly error: 'needs-kind';
36
+ readonly kinds: readonly SyncKind[];
37
+ } | {
38
+ readonly ok: false;
39
+ readonly error: string;
40
+ };
41
+ /** The card source the canvas stores (its BoardCardSource). */
42
+ export interface SyncSource {
43
+ readonly type: 'url' | 'paste';
44
+ readonly ref: string;
45
+ readonly title?: string;
46
+ }
47
+ /** The canvas inbox, as this package sees it. */
48
+ export interface CanvasInboxLike {
49
+ current(): string | null;
50
+ listCanvases(): Promise<SyncCanvas[]>;
51
+ addCard(sessionId: string, request: {
52
+ canvasId: string;
53
+ text: string;
54
+ kind?: string;
55
+ source?: SyncSource;
56
+ }): Promise<SyncOutcome>;
57
+ openCard(canvasId: string, cardId: string): void;
58
+ }
59
+ /** The article a card comes from. */
60
+ export interface SyncArticle {
61
+ readonly title: string;
62
+ readonly url?: string;
63
+ }
64
+ /** The localized words a card needs. */
65
+ export interface SyncWords {
66
+ readonly conclusion: string;
67
+ readonly original: string;
68
+ readonly kindCheck: string;
69
+ readonly kindDiscussion: string;
70
+ }
71
+ /** The canvas the reader synced to last, when the browser remembers one. */
72
+ export declare function lastCanvas(): string | null;
73
+ /** Remember the canvas the reader just synced to. */
74
+ export declare function rememberCanvas(canvasId: string): void;
75
+ /** The structured source the canvas keeps (the model reads it). */
76
+ export declare function sourceOf(article: SyncArticle): SyncSource;
77
+ /**
78
+ * One note as card text: the excerpt (and its translation), the comment, the
79
+ * conclusions under it when asked for, and where it came from.
80
+ */
81
+ export declare function noteCardText(note: ReaderNote, article: SyncArticle, words: SyncWords, withReplies: boolean): string;
82
+ /** One discussion note as card text: its points (with ¶), the aside, where it came from. */
83
+ export declare function discussionCardText(discussion: ReaderDiscussion, article: SyncArticle, words: SyncWords): string;
84
+ //# sourceMappingURL=canvas-sync.d.ts.map
@@ -0,0 +1,59 @@
1
+ /** Where the last sync went, remembered per browser (a convenience only). */
2
+ const LAST_CANVAS_KEY = 'dsh-reader.lastCanvas';
3
+ /** The canvas the reader synced to last, when the browser remembers one. */
4
+ export function lastCanvas() {
5
+ try {
6
+ return globalThis.localStorage?.getItem(LAST_CANVAS_KEY) ?? null;
7
+ }
8
+ catch {
9
+ return null;
10
+ }
11
+ }
12
+ /** Remember the canvas the reader just synced to. */
13
+ export function rememberCanvas(canvasId) {
14
+ try {
15
+ globalThis.localStorage?.setItem(LAST_CANVAS_KEY, canvasId);
16
+ }
17
+ catch {
18
+ // A private window or blocked storage: the picker just preselects the first canvas.
19
+ }
20
+ }
21
+ /** The provenance line every synced card ends with. */
22
+ function provenance(article, words, para) {
23
+ const where = `— 《${article.title}》${para === undefined ? '' : ` ¶${String(para)}`}`;
24
+ return article.url === undefined ? where : `${where} · [${words.original}](${article.url})`;
25
+ }
26
+ /** The structured source the canvas keeps (the model reads it). */
27
+ export function sourceOf(article) {
28
+ return article.url === undefined
29
+ ? { type: 'paste', ref: article.title }
30
+ : { type: 'url', ref: article.url, title: article.title };
31
+ }
32
+ /**
33
+ * One note as card text: the excerpt (and its translation), the comment, the
34
+ * conclusions under it when asked for, and where it came from.
35
+ */
36
+ export function noteCardText(note, article, words, withReplies) {
37
+ const lines = note.anchor.quote.split('\n').map(line => `> ${line}`);
38
+ if (note.anchor.translated !== undefined)
39
+ lines.push(`> (${note.anchor.translated})`);
40
+ if (note.comment !== undefined)
41
+ lines.push('', note.comment);
42
+ if (withReplies) {
43
+ for (const reply of note.replies ?? [])
44
+ lines.push('', `${reply.by === 'agent' ? words.conclusion : ''}${reply.text}`);
45
+ }
46
+ lines.push('', provenance(article, words, note.anchor.block + 1));
47
+ return lines.join('\n');
48
+ }
49
+ /** One discussion note as card text: its points (with ¶), the aside, where it came from. */
50
+ export function discussionCardText(discussion, article, words) {
51
+ const head = `${discussion.kind === 'check' ? words.kindCheck : words.kindDiscussion}${discussion.title === '' ? '' : ` · ${discussion.title}`}`;
52
+ const lines = [head, ''];
53
+ for (const point of discussion.points)
54
+ lines.push(`- ${point.text}${point.para === undefined ? '' : `(¶${String(point.para)})`}`);
55
+ if (discussion.aside !== undefined)
56
+ lines.push('', discussion.aside);
57
+ lines.push('', provenance(article, words));
58
+ return lines.join('\n');
59
+ }
@@ -10,8 +10,10 @@
10
10
  import type { SessionId } from '@deepseek-ai/dsh-session/types';
11
11
  import type { RemoteResult } from '@deepseek-ai/dsh-typert-protocol';
12
12
  import type { GlobalStandardProps, InjectFace, PropsLocale, PropsStore } from '@deepseek-ai/dsh-client-ui-slots';
13
- import type { ReaderAddFailure, ReaderAddOutcome, ReaderAddRefusal, ReaderAnnotationOutcome, ReaderBackfillCandidate, ReaderEntryBodyView, ReaderEntryFetchState, ReaderBody, ReaderCapabilities, ReaderEntryTranslationView, ReaderImageResult, ReaderMutationOutcome, ReaderRefreshResult, ReaderRecentEntry, ReaderSentenceLearn, ReaderSourceSummary, ReaderStorageStats, ReaderTag } from '../types.ts';
13
+ import type { ReaderAddFailure, ReaderAddOutcome, ReaderAddRefusal, ReaderAnnotationOutcome, ReaderBackfillCandidate, ReaderEntryBodyView, ReaderEntryFetchState, ReaderBody, ReaderCapabilities, ReaderEntryTranslationView, ReaderImageResult, ReaderMutationOutcome, ReaderRefreshResult, ReaderRecentEntry, ReaderSentenceLearn, ReaderSourceSummary, ReaderStorageStats, ReaderTag, LocalTranscriptStatus, MediaCardStates, ReaderMedia, ReaderPolishView } from '../types.ts';
14
+ import type { ReaderEntryIdentity, ReaderEntryNotes, ReaderNote, ReaderNoteInput, ReaderNoteOutcome } from '../notes-types.ts';
14
15
  import type { createReaderStore } from './store.ts';
16
+ import type { CanvasInboxLike } from './canvas-sync.ts';
15
17
  export type { RemoteResult };
16
18
  /** The result of extracting one article, as the detail view consumes it. */
17
19
  export interface ReaderArticle {
@@ -48,6 +50,15 @@ export interface ReaderExtractedMeta {
48
50
  readonly title?: string;
49
51
  readonly excerpt?: string;
50
52
  }
53
+ /**
54
+ * Which leg of one entry fetch is running, reported between the awaits.
55
+ *
56
+ * The fetch is a relay — the host downloads, THIS process extracts, the host
57
+ * stores — and a long article makes each leg visible to the reader. The pane
58
+ * renders the phase (plus elapsed time) so a slow fetch reads as work in
59
+ * progress, not as a dead click.
60
+ */
61
+ export type ReaderFetchPhase = 'downloading' | 'extracting' | 'storing';
51
62
  /**
52
63
  * The business face every reader surface receives from its registration.
53
64
  *
@@ -140,9 +151,10 @@ export interface ReaderPaneInjected {
140
151
  /**
141
152
  * Fetch one entry's article, extract it in THIS process (the host has no
142
153
  * parser) and cache what came out. One call from the view's perspective; the
143
- * extracted title/excerpt ride along for the card upgrade.
154
+ * extracted title/excerpt ride along for the card upgrade. `onPhase` fires
155
+ * between the legs, so a slow fetch has something to show.
144
156
  */
145
- fetchEntryBody: (entryId: string, url: string) => Promise<ReaderEntryBodyView & ReaderExtractedMeta>;
157
+ fetchEntryBody: (entryId: string, url: string, onPhase?: (phase: ReaderFetchPhase) => void) => Promise<ReaderEntryBodyView & ReaderExtractedMeta>;
146
158
  /**
147
159
  * Fetch one image through the host, for an `<img>` the browser already
148
160
  * failed on (CORP/hotlink protection — the bytes answer 200 to a direct
@@ -254,6 +266,121 @@ export interface ReaderPaneInjected {
254
266
  * never cached, never thrown.
255
267
  */
256
268
  captureRemote: () => ReaderCaptureRemote | undefined;
269
+ /** One article's notes and the conversations that discussed it. */
270
+ entryNotes: (entryId: string) => Promise<RemoteResult<{
271
+ entry?: ReaderEntryNotes;
272
+ }>>;
273
+ /** How many notes each article carries. */
274
+ noteCounts: () => Promise<RemoteResult<{
275
+ counts: Record<string, number>;
276
+ }>>;
277
+ /** Keep one excerpt (and its comment). */
278
+ addNote: (request: ReaderNoteInput) => Promise<RemoteResult<ReaderNote | 'empty' | 'full'>>;
279
+ /** Change or clear one note's comment. */
280
+ updateNote: (request: {
281
+ entryId: string;
282
+ noteId: string;
283
+ comment: string;
284
+ }) => Promise<RemoteResult<ReaderNoteOutcome>>;
285
+ /** Delete one note. */
286
+ deleteNote: (request: {
287
+ entryId: string;
288
+ noteId: string;
289
+ }) => Promise<RemoteResult<ReaderNoteOutcome>>;
290
+ /** Add the reader's own line under a note. */
291
+ addReply: (request: {
292
+ entryId: string;
293
+ noteId: string;
294
+ text: string;
295
+ }) => Promise<RemoteResult<ReaderNoteOutcome>>;
296
+ /** Rewrite (or, with empty text, remove) one line under a note. */
297
+ editReply: (request: {
298
+ entryId: string;
299
+ noteId: string;
300
+ index: number;
301
+ text: string;
302
+ }) => Promise<RemoteResult<ReaderNoteOutcome>>;
303
+ /** Remember that a note or a discussion note became a canvas card. */
304
+ markSynced: (request: {
305
+ entryId: string;
306
+ noteId?: string;
307
+ discussionId?: string;
308
+ canvasId: string;
309
+ cardId: string;
310
+ canvasTitle: string;
311
+ }) => Promise<RemoteResult<ReaderNoteOutcome>>;
312
+ /** The canvas plugin's inbox, probed live; absent hides 「同步到画布」. */
313
+ canvasInbox: () => CanvasInboxLike | undefined;
314
+ /** Delete one discussion note. */
315
+ deleteDiscussion: (request: {
316
+ entryId: string;
317
+ discussionId: string;
318
+ }) => Promise<RemoteResult<ReaderNoteOutcome>>;
319
+ /** Remember that this conversation discussed the article. */
320
+ recordDiscussion: (request: ReaderEntryIdentity & {
321
+ sessionId: string;
322
+ sessionTitle?: string;
323
+ }) => Promise<RemoteResult<'ok' | 'empty'>>;
324
+ /** Hand the host the article's original text for the model. */
325
+ snapshotEntry: (request: ReaderEntryIdentity & {
326
+ paragraphs: string[];
327
+ }) => Promise<RemoteResult<'ok' | 'empty'>>;
328
+ /** A video entry's transcript from its platform captions (stored; `none` when it has none). */
329
+ mediaTranscript: (request: {
330
+ entryId: string;
331
+ url: string;
332
+ }) => Promise<RemoteResult<{
333
+ entryId: string;
334
+ html?: string;
335
+ captions: 'platform' | 'none' | 'failed';
336
+ language?: string;
337
+ error?: string;
338
+ }>>;
339
+ /** Start (or rejoin) local transcription of an entry without captions; `unavailable` without the engine, `needs-models` until the first download is confirmed. */
340
+ startLocalTranscript: (request: {
341
+ entryId: string;
342
+ url: string;
343
+ media: ReaderMedia;
344
+ title?: string;
345
+ confirmModels?: boolean;
346
+ }) => Promise<RemoteResult<LocalTranscriptStatus>>;
347
+ /** Where an entry's local transcription stands, with the transcript so far. */
348
+ localTranscript: (request: {
349
+ entryId: string;
350
+ }) => Promise<RemoteResult<LocalTranscriptStatus>>;
351
+ /** The article's 润色 layer, when there is one for its current text. */
352
+ polish: (request: {
353
+ entryId: string;
354
+ }) => Promise<RemoteResult<ReaderPolishView | null>>;
355
+ /** Show one paragraph's machine translation instead of the 润色 (or back). */
356
+ setPolishOff: (request: {
357
+ entryId: string;
358
+ paragraph: number;
359
+ off: boolean;
360
+ }) => Promise<RemoteResult<ReaderPolishView | null>>;
361
+ /** The reader's names for a transcript's speakers, by index. */
362
+ speakerNames: (request: {
363
+ entryId: string;
364
+ }) => Promise<RemoteResult<Record<string, string>>>;
365
+ /** Name one speaker (empty: back to the default); answers all the names. */
366
+ nameSpeaker: (request: {
367
+ entryId: string;
368
+ speaker: number;
369
+ name: string;
370
+ }) => Promise<RemoteResult<Record<string, string>>>;
371
+ /** The wall's video / audio cards: what each one's transcript is. */
372
+ mediaStates: (request: {
373
+ entryIds: string[];
374
+ }) => Promise<RemoteResult<MediaCardStates>>;
375
+ /** Leave (withdraws a queued job) or, with `force`, stop an entry's local transcription. */
376
+ cancelLocalTranscript: (request: {
377
+ entryId: string;
378
+ force: boolean;
379
+ }) => Promise<RemoteResult<LocalTranscriptStatus>>;
380
+ /** Whether a conversation can be opened from the pane (probed live). */
381
+ canOpenSession: () => boolean;
382
+ /** Open a conversation in the main area; a no-op when the seam is absent. */
383
+ openSession: (sessionId: string) => void;
257
384
  }
258
385
  /** Full props of the reader pane: the seat's session + store + locale + face. */
259
386
  export type ReaderPaneProps = {
@@ -14,7 +14,10 @@
14
14
  * - {@link normalizeRichText} takes markup that is ALREADY known to be a body
15
15
  * (a feed's `content:encoded`, or the block extraction selected) and puts it
16
16
  * through a tag whitelist, so the renderer never sees third-party scripts,
17
- * styles, forms or event handlers.
17
+ * styles, forms or event handlers. The one sanctioned exception: a content
18
+ * embed (`embedElement`) survives as a sandboxed iframe — `allow-scripts`
19
+ * without `allow-same-origin`, an opaque origin that cannot see the pane —
20
+ * or as a native controls-only media element.
18
21
  *
19
22
  * Everything runs in the browser because the host runtime has no DOM parser at
20
23
  * all; nothing here touches the network.
@@ -73,6 +76,40 @@ export declare function extractArticle(pageHtml: string, baseUrl: string): Extra
73
76
  * @returns whitelisted HTML, or an empty string when nothing survives.
74
77
  */
75
78
  export declare function normalizeRichText(html: string, baseUrl?: string): string;
79
+ /**
80
+ * Rewrite every `var(--…)` in a kept SVG value to something that paints here.
81
+ *
82
+ * A statically served chart colors itself through the PAGE's custom
83
+ * properties (`style="stroke:var(--line)"`), defined in a stylesheet the
84
+ * extraction never carries. Left as-is, each reference resolves against the
85
+ * reader's document instead: an undefined one drops the stroke (an axis-less
86
+ * scatter of black dots — claude.dev's Figure 1), and a name that collides
87
+ * with a host token paints in a color nobody chose. So no reference survives:
88
+ * a declared fallback wins, a ground-like name becomes `none` (callout discs
89
+ * stop hiding what lies under them), and every other one becomes
90
+ * `currentColor` — the article's ink, legible in both themes. A
91
+ * capture-rendered page never reaches this: its styles arrive computed.
92
+ *
93
+ * @param value - one attribute value.
94
+ * @returns the value with no custom-property reference left.
95
+ */
96
+ export declare function resolveSvgVars(value: string): string;
97
+ /**
98
+ * Bring a body stored by an older extractor up to the current rules.
99
+ *
100
+ * Bodies are extracted once and cached, so the fixes above that can be
101
+ * replayed on whitelisted markup (`resolveSvgVars`, `isResponsiveAlternate`,
102
+ * `settleMathJaxTwins`, `settleListLabels`) would otherwise reach only
103
+ * articles fetched after they shipped — a cached claude.dev post kept drawing
104
+ * its figure as bare dots until its TTL ran out. This replays them at display
105
+ * time. A body with no `<svg>` or `<li>`, or one the current extractor wrote,
106
+ * comes back as the same string. (What the old extractor DROPPED — a
107
+ * foreignObject's labels — is not here to restore; that takes a refetch.)
108
+ *
109
+ * @param html - a stored (already whitelisted) article body.
110
+ * @returns the body under the current rules.
111
+ */
112
+ export declare function repairStoredBody(html: string): string;
76
113
  /**
77
114
  * The payload size that separates a real inlined figure from a 1px tracking
78
115
  * placeholder. A transparent-gif placeholder is ~70 characters of base64; the