@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,124 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /**
3
+ * The notes face of the detail view: the article's excerpts with their
4
+ * comments, the conversations that discussed it, and the sheet a new note is
5
+ * written in.
6
+ *
7
+ * One layout for every width (docs/panel-design.md): the list is a single
8
+ * column that only tightens under the panel's compact container, and the
9
+ * sheet rises from the bottom of the pane on a desk and on a phone alike.
10
+ *
11
+ * @module @khorsheed/dsh-reader/client/NotesView
12
+ */
13
+ import { useState } from 'react';
14
+ import css from './NotesView.module.css';
15
+ import { formatClock } from "../captions.js";
16
+ /** True when the keystroke is ⌘⏎ / Ctrl+⏎ outside an IME composition. */
17
+ function isSubmit(event) {
18
+ return event.key === 'Enter' && (event.metaKey || event.ctrlKey) && !event.nativeEvent.isComposing;
19
+ }
20
+ /** A one-field inline editor (a reply being added or rewritten). */
21
+ function LineEditor({ t, initial, onSave, onCancel }) {
22
+ const [draft, setDraft] = useState(initial);
23
+ const save = async () => { if (draft.trim() !== '' && await onSave(draft))
24
+ onCancel(); };
25
+ return (_jsxs("div", { className: css.editor, children: [_jsx("textarea", { autoFocus: true, className: css.area, value: draft, placeholder: t('notes.replyPlaceholder'), onChange: event => { setDraft(event.target.value); }, onKeyDown: event => {
26
+ if (isSubmit(event)) {
27
+ event.preventDefault();
28
+ void save();
29
+ }
30
+ if (event.key === 'Escape' && !event.nativeEvent.isComposing)
31
+ onCancel();
32
+ } }), _jsxs("div", { className: css.row, children: [_jsx("span", { className: css.spacer }), _jsx("button", { type: "button", className: css.ghost, onClick: onCancel, children: t('action.cancel') }), _jsx("button", { type: "button", className: css.primary, onClick: () => { void save(); }, children: t('notes.save') })] })] }));
33
+ }
34
+ /** 「已在《…》」: where an item went last, when it was synced. */
35
+ function SyncedMark({ t, synced }) {
36
+ const last = synced?.[synced.length - 1];
37
+ return last === undefined ? null : _jsx("span", { className: css.synced, children: t('notes.syncedIn', { canvas: last.canvasTitle }) });
38
+ }
39
+ /** Who wrote a line under a note, as the thread labels it. */
40
+ function replyAuthor(t, reply) {
41
+ if (reply.by === 'agent')
42
+ return t('notes.replyAgent');
43
+ return reply.editedFrom === 'agent' ? t('notes.replyEdited') : t('notes.replyUser');
44
+ }
45
+ /** One note row: excerpt, comment, the conclusions under it, and the gestures on it. */
46
+ function NoteRow({ t, note, missing, selecting, selected, canOpenSession, onToggle, onJump, onEdit, onDelete, onAddReply, onEditReply, onOpenSession, onSync, onPlay }) {
47
+ const [adding, setAdding] = useState(false);
48
+ const [rewriting, setRewriting] = useState(null);
49
+ const replies = note.replies ?? [];
50
+ const [editing, setEditing] = useState(false);
51
+ const [draft, setDraft] = useState(note.comment ?? '');
52
+ const [confirming, setConfirming] = useState(false);
53
+ const save = async () => {
54
+ if (await onEdit(draft))
55
+ setEditing(false);
56
+ };
57
+ return (_jsxs("div", { className: css.note, "data-selected": selected ? 'true' : undefined, children: [_jsxs("div", { className: css.noteHead, children: [selecting && (_jsx("input", { type: "checkbox", className: css.check, checked: selected, "aria-label": note.anchor.quote, onChange: onToggle })), _jsx("button", { type: "button", className: css.excerpt, onClick: selecting ? onToggle : onJump, title: missing ? t('notes.notFound') : undefined, children: note.anchor.quote })] }), note.anchor.translated !== undefined && (_jsxs("div", { className: css.translated, children: [_jsx("span", { className: css.label, children: t('notes.inTranslation') }), note.anchor.translated] })), editing
58
+ ? (_jsxs("div", { className: css.editor, children: [_jsx("textarea", { autoFocus: true, className: css.area, value: draft, placeholder: t('notes.commentPlaceholder'), onChange: event => { setDraft(event.target.value); }, onKeyDown: event => {
59
+ if (isSubmit(event)) {
60
+ event.preventDefault();
61
+ void save();
62
+ }
63
+ if (event.key === 'Escape' && !event.nativeEvent.isComposing) {
64
+ setEditing(false);
65
+ setDraft(note.comment ?? '');
66
+ }
67
+ } }), _jsxs("div", { className: css.row, children: [_jsx("span", { className: css.spacer }), _jsx("button", { type: "button", className: css.ghost, onClick: () => { setEditing(false); setDraft(note.comment ?? ''); }, children: t('action.cancel') }), _jsx("button", { type: "button", className: css.primary, onClick: () => { void save(); }, children: t('notes.save') })] })] }))
68
+ : note.comment !== undefined && _jsx("div", { className: css.comment, children: note.comment }), !selecting && (replies.length > 0 || adding) && (_jsxs("div", { className: css.thread, children: [replies.map((reply, index) => rewriting === index
69
+ ? (_jsx(LineEditor, { t: t, initial: reply.text, onSave: text => onEditReply(index, text), onCancel: () => { setRewriting(null); } }, `${reply.at}-${String(index)}`))
70
+ : (_jsxs("div", { className: css.reply, children: [_jsxs("div", { className: css.replyWho, "data-agent": reply.by === 'agent' ? 'true' : undefined, children: [_jsx("span", { children: replyAuthor(t, reply) }), reply.sessionId !== undefined && canOpenSession && (_jsxs("button", { type: "button", className: css.link, onClick: () => { onOpenSession(reply.sessionId); }, children: ["\u2197 ", t('notes.backToSession')] })), _jsx("span", { className: css.spacer }), _jsx("button", { type: "button", className: css.link, onClick: () => { setRewriting(index); }, children: t('notes.replyEdit') }), _jsx("button", { type: "button", className: css.link, onClick: () => { void onEditReply(index, ''); }, children: t('notes.replyRemove') })] }), _jsx("div", { className: css.replyText, children: reply.text })] }, `${reply.at}-${String(index)}`))), adding && _jsx(LineEditor, { t: t, initial: "", onSave: onAddReply, onCancel: () => { setAdding(false); } })] })), _jsxs("div", { className: css.foot, children: [note.anchor.time !== undefined && onPlay !== undefined && (_jsxs("button", { type: "button", className: css.play, onClick: onPlay, children: ["\u25B6 ", formatClock(note.anchor.time)] })), _jsxs("span", { children: ["\u00B6", note.anchor.block + 1] }), missing && _jsx("span", { className: css.missing, children: t('notes.notFound') }), _jsx(SyncedMark, { t: t, synced: note.syncedTo }), _jsx("span", { className: css.spacer }), !selecting && !editing && (confirming
71
+ ? (_jsxs(_Fragment, { children: [_jsx("span", { children: t('notes.deleteConfirm') }), _jsx("button", { type: "button", className: css.link, onClick: () => { setConfirming(false); }, children: t('action.cancel') }), _jsx("button", { type: "button", className: `${css.link} ${css.danger}`, onClick: onDelete, children: t('notes.delete') })] }))
72
+ : (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: css.link, onClick: () => { setAdding(true); }, children: t('notes.replyAdd') }), onSync !== undefined && _jsx("button", { type: "button", className: css.link, onClick: onSync, children: t('notes.sync') }), _jsx("button", { type: "button", className: css.link, onClick: () => { setDraft(note.comment ?? ''); setEditing(true); }, children: t('notes.edit') }), _jsx("button", { type: "button", className: css.link, onClick: () => { setConfirming(true); }, children: t('notes.delete') })] })))] })] }));
73
+ }
74
+ /** One discussion note: what a conversation (or a quiz-me round) came to. */
75
+ function DiscussionCard({ t, discussion, canOpenSession, onOpenSession, onJumpPara, onDelete, onSync }) {
76
+ const [confirming, setConfirming] = useState(false);
77
+ const check = discussion.kind === 'check';
78
+ return (_jsxs("div", { className: css.discussion, children: [_jsxs("div", { className: css.discussionHead, children: [_jsx("span", { className: css.kind, "data-kind": discussion.kind, children: check ? t('notes.kindCheck') : t('notes.kindDiscussion') }), _jsx("b", { className: css.discussionTitle, children: discussion.title !== '' ? discussion.title : discussion.createdAt.slice(0, 10) }), discussion.sessionId !== undefined && canOpenSession && (_jsxs("button", { type: "button", className: css.link, onClick: () => { onOpenSession(discussion.sessionId); }, children: [t('notes.backToSession'), " \u2197"] }))] }), _jsx("div", { className: css.pointsLabel, "data-kind": discussion.kind, children: check ? t('notes.pointsCheck') : t('notes.pointsDiscussion') }), _jsx("ul", { className: css.points, children: discussion.points.map((point, index) => (_jsxs("li", { children: [point.text, point.para !== undefined && (_jsxs(_Fragment, { children: [' ', _jsxs("button", { type: "button", className: css.link, onClick: () => { onJumpPara(point.para); }, children: ["\u00B6", point.para, " \u2197"] })] }))] }, String(index)))) }), discussion.aside !== undefined && _jsx("div", { className: css.aside, children: discussion.aside }), _jsxs("div", { className: css.foot, children: [_jsx("span", { children: discussion.title !== '' ? discussion.createdAt.slice(0, 10) : '' }), _jsx(SyncedMark, { t: t, synced: discussion.syncedTo }), _jsx("span", { className: css.spacer }), !confirming && onSync !== undefined && _jsx("button", { type: "button", className: css.link, onClick: onSync, children: t('notes.sync') }), confirming
79
+ ? (_jsxs(_Fragment, { children: [_jsx("span", { children: t('notes.deleteConfirm') }), _jsx("button", { type: "button", className: css.link, onClick: () => { setConfirming(false); }, children: t('action.cancel') }), _jsx("button", { type: "button", className: `${css.link} ${css.danger}`, onClick: onDelete, children: t('notes.delete') })] }))
80
+ : _jsx("button", { type: "button", className: css.link, onClick: () => { setConfirming(true); }, children: t('notes.delete') })] })] }));
81
+ }
82
+ /** The article's notes, and the conversations that discussed it. */
83
+ export function NotesView(props) {
84
+ const { t, entry } = props;
85
+ const [selecting, setSelecting] = useState(false);
86
+ const [selected, setSelected] = useState(new Set());
87
+ const notes = entry?.notes ?? [];
88
+ const sessions = entry?.sessions ?? [];
89
+ const discussions = entry?.discussions ?? [];
90
+ const toggle = (id) => {
91
+ setSelected(current => {
92
+ const next = new Set(current);
93
+ if (next.has(id))
94
+ next.delete(id);
95
+ else
96
+ next.add(id);
97
+ return next;
98
+ });
99
+ };
100
+ const endSelecting = () => { setSelecting(false); setSelected(new Set()); };
101
+ return (_jsxs("div", { className: css.view, children: [_jsxs("div", { className: css.body, children: [_jsxs("div", { className: css.section, children: [_jsx("span", { children: t('notes.excerpts') }), _jsx("span", { className: css.spacer }), notes.length > 0 && (_jsx("button", { type: "button", className: css.link, onClick: () => { if (selecting)
102
+ endSelecting();
103
+ else
104
+ setSelecting(true); }, children: selecting ? t('notes.selectDone') : t('notes.select') }))] }), notes.length === 0 && _jsx("p", { className: css.empty, children: t('notes.empty') }), notes.map(note => (_jsx(NoteRow, { t: t, note: note, missing: props.missing.has(note.id), selecting: selecting, selected: selected.has(note.id), onToggle: () => { toggle(note.id); }, onJump: () => { props.onJump(note); }, onEdit: comment => props.onEdit(note, comment), onDelete: () => { props.onDelete(note); }, canOpenSession: props.canOpenSession, onAddReply: text => props.onAddReply(note, text), onEditReply: (index, text) => props.onEditReply(note, index, text), onOpenSession: props.onOpenSession, onSync: props.sync.available ? () => { props.sync.open({ kind: 'notes', ids: [note.id] }); } : undefined, onPlay: props.onPlay === undefined ? undefined : () => { props.onPlay?.(note); } }, note.id))), _jsx("div", { className: css.section, children: _jsx("span", { children: t('notes.discussionNotes') }) }), discussions.length === 0 && _jsx("p", { className: css.empty, children: t('notes.noDiscussionNotes') }), discussions.map(discussion => (_jsx(DiscussionCard, { t: t, discussion: discussion, canOpenSession: props.canOpenSession, onOpenSession: props.onOpenSession, onJumpPara: props.onJumpPara, onDelete: () => { props.onDeleteDiscussion(discussion); }, onSync: props.sync.available ? () => { props.sync.open({ kind: 'discussion', id: discussion.id }); } : undefined }, discussion.id))), _jsx("div", { className: css.section, children: _jsx("span", { children: t('notes.discussions') }) }), sessions.length === 0 && _jsx("p", { className: css.empty, children: t('notes.noDiscussions') }), sessions.map(session => (_jsxs("div", { className: css.session, children: [_jsx("span", { className: css.sessionTitle, children: props.sessionTitle(session.sessionId) ?? session.title ?? t('notes.untitledSession') }), props.canOpenSession && (_jsxs("button", { type: "button", className: css.link, onClick: () => { props.onOpenSession(session.sessionId); }, children: [t('notes.discussedIn'), " \u2197"] }))] }, session.sessionId)))] }), props.sync.done !== null && (_jsxs("div", { className: css.syncDone, children: [_jsx("span", { children: t('notes.syncDone', { canvas: props.sync.done.title }) }), _jsx("button", { type: "button", className: css.link, onClick: props.sync.openDone, children: t('notes.syncOpen') })] })), props.sync.target !== null && (_jsx(SyncSheet, { t: t, sync: props.sync, count: props.sync.target.kind === 'notes' ? props.sync.target.ids.length : 1, hasReplies: props.sync.target.kind === 'notes'
105
+ && notes.some(note => props.sync.target.ids.includes(note.id) && (note.replies ?? []).length > 0) })), _jsx("div", { className: css.bar, children: selecting
106
+ ? (_jsxs(_Fragment, { children: [_jsx("span", { children: t('notes.selected', { count: selected.size }) }), _jsx("span", { className: css.spacer }), props.sync.available && (_jsx("button", { type: "button", className: css.secondary, disabled: selected.size === 0, onClick: () => { props.sync.open({ kind: 'notes', ids: [...selected] }); endSelecting(); }, children: t('notes.sync') })), _jsx("button", { type: "button", className: css.primary, disabled: selected.size === 0, onClick: () => { props.onDiscuss([...selected]); endSelecting(); }, children: t('notes.discuss') })] }))
107
+ : (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: `${css.secondary} ${css.wide}`, disabled: notes.length === 0, onClick: () => { props.onDiscuss([]); }, children: t('notes.discussAll') }), _jsx("button", { type: "button", className: `${css.primary} ${css.wide}`, onClick: props.onCheck, children: t('notes.check') })] })) })] }));
108
+ }
109
+ /** The 「同步到画布」 sheet: which canvas, and whether the conclusions come along. */
110
+ function SyncSheet({ t, sync, count, hasReplies }) {
111
+ return (_jsxs(_Fragment, { children: [_jsx("div", { className: css.scrim, onClick: sync.cancel }), _jsxs("div", { className: css.sheet, role: "dialog", "aria-label": t('notes.sync'), children: [_jsx("div", { className: css.grab }), _jsx("div", { className: css.sheetHead, children: _jsx("b", { children: t('notes.syncTitle', { count }) }) }), sync.canvases === null && _jsx("p", { className: css.empty, children: "\u2026" }), sync.canvases !== null && sync.canvases.length === 0 && _jsx("p", { className: css.empty, children: t('notes.syncNoCanvas') }), sync.canvases !== null && sync.canvases.length > 0 && (_jsx("div", { className: css.picker, role: "radiogroup", "aria-label": t('notes.sync'), children: sync.canvases.map(canvas => (_jsxs("button", { type: "button", role: "radio", "aria-checked": sync.pick === canvas.id, className: css.option, onClick: () => { sync.setPick(canvas.id); }, children: [_jsx("span", { className: css.optionTitle, children: canvas.title }), _jsxs("span", { className: css.optionMeta, children: [canvas.id === sync.last ? `${t('notes.syncLast')} · ` : '', t('notes.syncCards', { count: canvas.cardCount })] })] }, canvas.id))) })), sync.target?.kind === 'notes' && hasReplies && (_jsxs("label", { className: css.checkRow, children: [_jsx("input", { type: "checkbox", checked: sync.withReplies, onChange: event => { sync.setWithReplies(event.target.checked); } }), t('notes.syncReplies')] })), sync.kinds !== null && (_jsxs("div", { className: css.kinds, children: [_jsx("span", { className: css.hint, children: t('notes.syncKind') }), _jsx("select", { className: css.select, value: sync.kind ?? '', onChange: event => { sync.setKind(event.target.value); }, children: sync.kinds.map(item => _jsx("option", { value: item.id, children: item.label }, item.id)) })] })), _jsxs("div", { className: css.row, children: [sync.error !== null && _jsx("span", { className: css.failed, children: t('notes.syncFailed', { reason: sync.error }) }), _jsx("span", { className: css.spacer }), _jsx("button", { type: "button", className: css.ghost, onClick: sync.cancel, children: t('action.cancel') }), _jsx("button", { type: "button", className: css.primary, disabled: sync.busy || sync.pick === null, onClick: sync.run, children: t('notes.syncRun') })] })] })] }));
112
+ }
113
+ /** The sheet a new note is written in. */
114
+ export function NoteSheet({ t, selection, saving, failed, onSave, onCancel }) {
115
+ const [comment, setComment] = useState('');
116
+ return (_jsxs(_Fragment, { children: [_jsx("div", { className: css.scrim, onClick: onCancel }), _jsxs("div", { className: css.sheet, role: "dialog", "aria-label": t('notes.sheetTitle'), children: [_jsx("div", { className: css.grab }), _jsxs("div", { className: css.sheetHead, children: [_jsx("b", { children: t('notes.sheetTitle') }), _jsx("span", { className: css.chip, children: t('notes.excerpt') })] }), _jsx("div", { className: css.sheetExcerpt, children: selection.translated ?? selection.quote }), _jsx("textarea", { autoFocus: true, className: css.area, value: comment, placeholder: t('notes.commentPlaceholder'), onChange: event => { setComment(event.target.value); }, onKeyDown: event => {
117
+ if (isSubmit(event)) {
118
+ event.preventDefault();
119
+ onSave(comment);
120
+ }
121
+ if (event.key === 'Escape' && !event.nativeEvent.isComposing)
122
+ onCancel();
123
+ } }), _jsxs("div", { className: css.row, children: [_jsx("span", { className: failed ? css.failed : css.hint, children: failed ? t('notes.saveFailed') : t('notes.commentOptional') }), _jsx("span", { className: css.spacer }), _jsx("button", { type: "button", className: css.ghost, onClick: onCancel, children: t('action.cancel') }), _jsxs("button", { type: "button", className: css.primary, disabled: saving, onClick: () => { onSave(comment); }, children: [t('notes.save'), " \u2318\u23CE"] })] })] })] }));
124
+ }
@@ -10,8 +10,10 @@
10
10
  * round trip.
11
11
  * - **the detail view** — clicking a card opens it. This is the whole point:
12
12
  * the body renders as DOM text, which is what makes passage quoting work
13
- * (the QUOTE plugin's own selection menu sees it), and it is why the pane
14
- * never hosts a sandboxed iframe.
13
+ * (the QUOTE plugin's own selection menu sees it). The one non-text island:
14
+ * an article's content embeds survive as sandboxed iframes (opaque origin,
15
+ * no `allow-same-origin`) or controls-only media elements — neither can see
16
+ * this page, and neither carries quotable text.
15
17
  * - **the add dialog** — an overlay in the members-tab idiom, because adding is
16
18
  * a momentary act and the wall should stay where it was behind it. The host
17
19
  * decides whether a pasted URL is a feed or an article from what comes back
@@ -30,6 +32,17 @@
30
32
  import { type ReactNode } from 'react';
31
33
  import type { ReaderPaneProps } from './contract.ts';
32
34
  import { type ReaderReadingAnchor } from './session.ts';
35
+ /**
36
+ * Whether a fetch failure is one a real browser gets past: a bot challenge,
37
+ * a 401/403 that refuses non-browser requests, or a cookie handshake
38
+ * (Springer Nature's idp bounce). These are the reasons the
39
+ * rendered fetch (the capture package's headless Chrome) exists to answer; a
40
+ * login wall is not among them — a cookie-less browser meets it too.
41
+ *
42
+ * @param reason - the host's recorded message.
43
+ * @returns true when escalating to the rendered fetch is worth a try.
44
+ */
45
+ export declare function browserSolvable(reason: string): boolean;
33
46
  /**
34
47
  * One top-level block, measured.
35
48
  *
@@ -66,6 +79,10 @@ export interface ArticleBlockMetrics {
66
79
  * @returns pixels into the block.
67
80
  */
68
81
  export declare function offsetInBlock(block: ArticleBlockMetrics, anchor: ReaderReadingAnchor): number;
69
- /** The reader tab body. */
82
+ /**
83
+ * The reader tab body, inside the shared panel root: the `dsh-panel` size
84
+ * container its compact rules query, and the compact flag the wall's and the
85
+ * detail's headers read (panel-responsive-spec P2).
86
+ */
70
87
  export declare function ReaderPane(props: ReaderPaneProps): ReactNode;
71
88
  //# sourceMappingURL=ReaderPane.d.ts.map