@khorsheed/dsh-library 0.0.0-stage → 0.1.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 (130) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.en.md +49 -0
  3. package/README.i18n.yaml +6 -0
  4. package/README.md +48 -2
  5. package/cordis.patch.yml +17 -0
  6. package/lib/client.js +66334 -0
  7. package/lib/index.js +3318 -0
  8. package/lib/profiles-CFrPQdfO.js +1692 -0
  9. package/lib/tool.js +1597 -0
  10. package/lib/typert.host.d.ts +3 -0
  11. package/lib/typert.host.js +3811 -0
  12. package/lib/typert.remote-client.d.ts +137 -0
  13. package/lib/typert.remote-client.js +3077 -0
  14. package/lib/types/cards.d.ts +41 -0
  15. package/lib/types/cards.js +111 -0
  16. package/lib/types/client/LibraryPane.d.ts +26 -0
  17. package/lib/types/client/LibraryPane.js +129 -0
  18. package/lib/types/client/bind/BindChip.d.ts +33 -0
  19. package/lib/types/client/bind/BindChip.js +102 -0
  20. package/lib/types/client/contract.d.ts +167 -0
  21. package/lib/types/client/contract.js +1 -0
  22. package/lib/types/client/covers.d.ts +36 -0
  23. package/lib/types/client/covers.js +66 -0
  24. package/lib/types/client/definition.d.ts +27 -0
  25. package/lib/types/client/definition.js +32 -0
  26. package/lib/types/client/icons-local.d.ts +41 -0
  27. package/lib/types/client/icons-local.js +25 -0
  28. package/lib/types/client/icons.d.ts +27 -0
  29. package/lib/types/client/icons.js +11 -0
  30. package/lib/types/client/index.d.ts +35 -0
  31. package/lib/types/client/index.js +231 -0
  32. package/lib/types/client/judge.d.ts +70 -0
  33. package/lib/types/client/judge.js +262 -0
  34. package/lib/types/client/locales.d.ts +553 -0
  35. package/lib/types/client/locales.js +1076 -0
  36. package/lib/types/client/look.d.ts +63 -0
  37. package/lib/types/client/look.js +64 -0
  38. package/lib/types/client/math.d.ts +24 -0
  39. package/lib/types/client/math.js +47 -0
  40. package/lib/types/client/mistakes.d.ts +21 -0
  41. package/lib/types/client/mistakes.js +28 -0
  42. package/lib/types/client/preset-visibility.d.ts +109 -0
  43. package/lib/types/client/preset-visibility.js +123 -0
  44. package/lib/types/client/quiz-drafts.d.ts +29 -0
  45. package/lib/types/client/quiz-drafts.js +50 -0
  46. package/lib/types/client/reveal.d.ts +21 -0
  47. package/lib/types/client/reveal.js +31 -0
  48. package/lib/types/client/store.d.ts +111 -0
  49. package/lib/types/client/store.js +48 -0
  50. package/lib/types/client/titles.d.ts +17 -0
  51. package/lib/types/client/titles.js +23 -0
  52. package/lib/types/client/ui.d.ts +96 -0
  53. package/lib/types/client/ui.js +124 -0
  54. package/lib/types/client/views/book.d.ts +57 -0
  55. package/lib/types/client/views/book.js +164 -0
  56. package/lib/types/client/views/cards.d.ts +17 -0
  57. package/lib/types/client/views/cards.js +53 -0
  58. package/lib/types/client/views/home.d.ts +26 -0
  59. package/lib/types/client/views/home.js +263 -0
  60. package/lib/types/client/views/lesson.d.ts +42 -0
  61. package/lib/types/client/views/lesson.js +146 -0
  62. package/lib/types/client/views/path.d.ts +32 -0
  63. package/lib/types/client/views/path.js +64 -0
  64. package/lib/types/client/views/quiz.d.ts +42 -0
  65. package/lib/types/client/views/quiz.js +366 -0
  66. package/lib/types/client/views/station-quiz.d.ts +43 -0
  67. package/lib/types/client/views/station-quiz.js +203 -0
  68. package/lib/types/client/views/station.d.ts +18 -0
  69. package/lib/types/client/views/station.js +142 -0
  70. package/lib/types/image-refs.d.ts +35 -0
  71. package/lib/types/image-refs.js +71 -0
  72. package/lib/types/index.d.ts +51 -0
  73. package/lib/types/index.js +31 -0
  74. package/lib/types/mastery.d.ts +81 -0
  75. package/lib/types/mastery.js +110 -0
  76. package/lib/types/profiles.d.ts +31 -0
  77. package/lib/types/profiles.js +25 -0
  78. package/lib/types/remote.d.ts +355 -0
  79. package/lib/types/remote.js +332 -0
  80. package/lib/types/service/assess.d.ts +144 -0
  81. package/lib/types/service/assess.js +773 -0
  82. package/lib/types/service/bindings.d.ts +92 -0
  83. package/lib/types/service/bindings.js +198 -0
  84. package/lib/types/service/cards.d.ts +85 -0
  85. package/lib/types/service/cards.js +320 -0
  86. package/lib/types/service/core.d.ts +124 -0
  87. package/lib/types/service/core.js +169 -0
  88. package/lib/types/service/pages.d.ts +60 -0
  89. package/lib/types/service/pages.js +125 -0
  90. package/lib/types/service/roadmap.d.ts +99 -0
  91. package/lib/types/service/roadmap.js +284 -0
  92. package/lib/types/service/rules.d.ts +63 -0
  93. package/lib/types/service/rules.js +245 -0
  94. package/lib/types/service/shelf.d.ts +136 -0
  95. package/lib/types/service/shelf.js +422 -0
  96. package/lib/types/service.d.ts +42 -0
  97. package/lib/types/service.js +41 -0
  98. package/lib/types/store.d.ts +257 -0
  99. package/lib/types/store.js +1376 -0
  100. package/lib/types/tool-definition.d.ts +49 -0
  101. package/lib/types/tool-definition.js +49 -0
  102. package/lib/types/tool.d.ts +71 -0
  103. package/lib/types/tool.js +174 -0
  104. package/lib/types/tools/assess.d.ts +11 -0
  105. package/lib/types/tools/assess.js +82 -0
  106. package/lib/types/tools/binding.d.ts +11 -0
  107. package/lib/types/tools/binding.js +46 -0
  108. package/lib/types/tools/cards.d.ts +11 -0
  109. package/lib/types/tools/cards.js +100 -0
  110. package/lib/types/tools/kit.d.ts +215 -0
  111. package/lib/types/tools/kit.js +281 -0
  112. package/lib/types/tools/notes.d.ts +11 -0
  113. package/lib/types/tools/notes.js +40 -0
  114. package/lib/types/tools/page.d.ts +11 -0
  115. package/lib/types/tools/page.js +59 -0
  116. package/lib/types/tools/project.d.ts +11 -0
  117. package/lib/types/tools/project.js +67 -0
  118. package/lib/types/tools/quiz.d.ts +11 -0
  119. package/lib/types/tools/quiz.js +74 -0
  120. package/lib/types/tools/read.d.ts +11 -0
  121. package/lib/types/tools/read.js +127 -0
  122. package/lib/types/tools/resource.d.ts +11 -0
  123. package/lib/types/tools/resource.js +147 -0
  124. package/lib/types/tools/roadmap.d.ts +11 -0
  125. package/lib/types/tools/roadmap.js +102 -0
  126. package/lib/types/types.d.ts +752 -0
  127. package/lib/types/types.js +159 -0
  128. package/locale/en.json +6 -0
  129. package/locale/zh.json +6 -0
  130. package/package.json +135 -3
@@ -0,0 +1,66 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { coverKind, coverTopic, isThickResource } from "../types.js";
3
+ import css from './LibraryPane.module.css';
4
+ /** A cover's one-line label. */
5
+ export function coverLabel(cover, context, t) {
6
+ switch (coverKind(cover)) {
7
+ case 'resource': {
8
+ const resource = context.resources.find(candidate => candidate.id === cover.ref);
9
+ if (resource === undefined)
10
+ return cover.locator === undefined ? cover.ref : `${cover.ref} ${t('cover.chapters', { ranges: cover.locator })}`;
11
+ const title = isThickResource(resource) ? `《${resource.title}》` : resource.title;
12
+ return cover.locator === undefined ? title : `${title} ${t('cover.chapters', { ranges: cover.locator })}`;
13
+ }
14
+ case 'url':
15
+ return cover.title ?? cover.ref.replace(/^https?:\/\//, '').split('/')[0] ?? cover.ref;
16
+ case 'topic':
17
+ return `${t('cover.topic')}: ${coverTopic(cover) ?? ''}`;
18
+ }
19
+ }
20
+ /** The kind tag on a cover chip: book / link / teaching. */
21
+ function coverKindLabel(cover, context, t) {
22
+ const kind = coverKind(cover);
23
+ if (kind === 'resource') {
24
+ const resource = context.resources.find(candidate => candidate.id === cover.ref);
25
+ return resource !== undefined && isThickResource(resource) ? t('cover.book') : t('cover.link');
26
+ }
27
+ return kind === 'topic' ? t('cover.topic') : t('cover.link');
28
+ }
29
+ /** A cover's provenance badge (absent on a shelf resource — its tocSource already records it). */
30
+ export function ProvenanceBadge({ cover, t }) {
31
+ if (cover.by === undefined)
32
+ return null;
33
+ return _jsx("span", { className: css.prov, "data-by": cover.by, children: t(`prov.${cover.by}`) });
34
+ }
35
+ /** One station's covers as a chip row: label + provenance badge each. */
36
+ export function CoverChips({ covers, context, t }) {
37
+ if (covers.length === 0)
38
+ return null;
39
+ return (_jsx("div", { className: css.chips, children: covers.map((cover, index) => (_jsxs("span", { className: css.chip, "data-tone": coverKind(cover) === 'resource' ? 'photo' : coverKind(cover) === 'topic' ? undefined : 'web', children: [coverKindLabel(cover, context, t), " \u00B7 ", coverLabel(cover, context, t), _jsx(ProvenanceBadge, { cover: cover, t: t })] }, `${coverKind(cover)}-${index}`))) }));
40
+ }
41
+ /** One line for a path row: 「《书》 第 3-5 章 · 链接 ×2 · 讲授 ×1」. */
42
+ export function summarizeCovers(covers, context, t) {
43
+ const thick = [];
44
+ let links = 0;
45
+ let topics = 0;
46
+ for (const cover of covers) {
47
+ const kind = coverKind(cover);
48
+ if (kind === 'resource') {
49
+ const resource = context.resources.find(candidate => candidate.id === cover.ref);
50
+ if (resource !== undefined && isThickResource(resource))
51
+ thick.push(coverLabel(cover, context, t));
52
+ else
53
+ links += 1;
54
+ }
55
+ else if (kind === 'url')
56
+ links += 1;
57
+ else
58
+ topics += 1;
59
+ }
60
+ const parts = [...thick];
61
+ if (links > 0)
62
+ parts.push(`${t('cover.link')} ×${links}`);
63
+ if (topics > 0)
64
+ parts.push(`${t('cover.topic')} ×${topics}`);
65
+ return parts.join(' · ');
66
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Stage one of the library's right-Sidebar registration: the tab type, the
3
+ * body of which is registered in `client/index.ts`.
4
+ *
5
+ * The kind is MINTED (`library`) rather than taken over: this is a page type
6
+ * of its own, not a superset of an official one, so it claims no address and
7
+ * ships no `patterns` / `canOpen`. It joins the guide page as its own card,
8
+ * which is how a learner finds it before pinning the tab.
9
+ *
10
+ * @module @khorsheed/dsh-library/client/definition
11
+ */
12
+ import type { SidebarRightTabDefinition } from '@deepseek-ai/dsh-client-ui-sidebar-right/client';
13
+ import type { TranslateNS } from '@deepseek-ai/dsh-client-locale/client';
14
+ /** The tab kind: minted, since no official kind means "library". */
15
+ export declare const LIBRARY_KIND = "library";
16
+ /** This implementation's identity, and the key its body registers under. */
17
+ export declare const LIBRARY_TAB_ID = "@khorsheed/dsh-library";
18
+ /**
19
+ * The library type's registry definition. No `priority` (the default
20
+ * `extension` band is right for a type from outside the product) and no
21
+ * `patterns`/`canOpen` (a page type recognises no address).
22
+ *
23
+ * @param t - the library namespace's translator.
24
+ * @returns the definition to register.
25
+ */
26
+ export declare function libraryDefinition(t: TranslateNS<'library'>): SidebarRightTabDefinition;
27
+ //# sourceMappingURL=definition.d.ts.map
@@ -0,0 +1,32 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { IconBookOutlineMedium } from "./icons-local.js";
3
+ /** The tab kind: minted, since no official kind means "library". */
4
+ export const LIBRARY_KIND = 'library';
5
+ /** This implementation's identity, and the key its body registers under. */
6
+ export const LIBRARY_TAB_ID = '@khorsheed/dsh-library';
7
+ /** The type's glyph on the guide card. */
8
+ function LibraryGlyph({ size, className }) {
9
+ return _jsx(IconBookOutlineMedium, { size: size, className: className });
10
+ }
11
+ /**
12
+ * The library type's registry definition. No `priority` (the default
13
+ * `extension` band is right for a type from outside the product) and no
14
+ * `patterns`/`canOpen` (a page type recognises no address).
15
+ *
16
+ * @param t - the library namespace's translator.
17
+ * @returns the definition to register.
18
+ */
19
+ export function libraryDefinition(t) {
20
+ return {
21
+ id: LIBRARY_TAB_ID,
22
+ kind: LIBRARY_KIND,
23
+ title: () => t('tab.label'),
24
+ guide: [{
25
+ id: LIBRARY_KIND,
26
+ order: 62,
27
+ title: () => t('tab.label'),
28
+ description: () => t('guide.description'),
29
+ icon: LibraryGlyph,
30
+ }],
31
+ };
32
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Package-owned icons matching the official outline-icon conventions (16×16
3
+ * viewBox, `stroke="currentColor"` geometry) for glyphs ui-primitives does not
4
+ * ship — the supplement pattern message-tools and canvas established. Glyphs
5
+ * upstream does ship come from the generated icons.tsx
6
+ * (scripts/sync-icon-artwork.mts), which never touches this file.
7
+ *
8
+ * @module @khorsheed/dsh-library/client
9
+ */
10
+ /** Shared props for every product icon component (mirrors the upstream IconProps). */
11
+ export interface IconProps {
12
+ /** Square edge in px; defaults to the glyph's own drawn size. */
13
+ size?: number | undefined;
14
+ /** Extra class for layout placement; color rides currentColor. */
15
+ className?: string | undefined;
16
+ }
17
+ /** A book — the library's own glyph. */
18
+ export declare const IconBookOutlineMedium: ({ size, className }: IconProps) => import("react").JSX.Element;
19
+ /** A padlock — a locked roadmap node. */
20
+ export declare const IconLockOutlineMedium: ({ size, className }: IconProps) => import("react").JSX.Element;
21
+ /** A camera — photo provenance on a vocab entry. */
22
+ export declare const IconCameraOutlineMedium: ({ size, className }: IconProps) => import("react").JSX.Element;
23
+ /** Spark cards — the vocab book. */
24
+ export declare const IconCardsOutlineMedium: ({ size, className }: IconProps) => import("react").JSX.Element;
25
+ /** A pencil — editing a project. */
26
+ export declare const IconPenOutlineMedium: ({ size, className }: IconProps) => import("react").JSX.Element;
27
+ /** A page with lines — an article resource. */
28
+ export declare const IconArticleOutlineMedium: ({ size, className }: IconProps) => import("react").JSX.Element;
29
+ /** A box — a repo resource. */
30
+ export declare const IconRepoOutlineMedium: ({ size, className }: IconProps) => import("react").JSX.Element;
31
+ /** A gear — a tool resource. */
32
+ export declare const IconToolOutlineMedium: ({ size, className }: IconProps) => import("react").JSX.Element;
33
+ /** A graduation cap — a course resource. */
34
+ export declare const IconCourseOutlineMedium: ({ size, className }: IconProps) => import("react").JSX.Element;
35
+ /** A chip — a hardware resource. */
36
+ export declare const IconHardwareOutlineMedium: ({ size, className }: IconProps) => import("react").JSX.Element;
37
+ /** A speech bubble — ask the coach about a question. */
38
+ export declare const IconChatOutlineMedium: ({ size, className }: IconProps) => import("react").JSX.Element;
39
+ /** A flag — report a broken question. */
40
+ export declare const IconFlagOutlineMedium: ({ size, className }: IconProps) => import("react").JSX.Element;
41
+ //# sourceMappingURL=icons-local.d.ts.map
@@ -0,0 +1,25 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /** A book — the library's own glyph. */
3
+ export const IconBookOutlineMedium = ({ size = 14, className }) => (_jsx("svg", { width: size, height: size, className: className, viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", strokeWidth: 1.3, children: _jsx("path", { d: "M2.5 12.5V3.2C2.5 2.5 3.1 2 3.8 2H13.5V11.5H4C3.2 11.5 2.5 12.2 2.5 13C2.5 13.8 3.2 14.5 4 14.5H13.5", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", transform: "translate(0,-0.5)" }) }));
4
+ /** A padlock — a locked roadmap node. */
5
+ export const IconLockOutlineMedium = ({ size = 16, className }) => (_jsxs("svg", { width: size, height: size, className: className, viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", strokeWidth: 1.3, children: [_jsx("rect", { x: "3.2", y: "7", width: "9.6", height: "6.6", rx: "1.4", stroke: "currentColor" }), _jsx("path", { d: "M5.2 7V5.2C5.2 3.6 6.4 2.4 8 2.4C9.6 2.4 10.8 3.6 10.8 5.2V7", stroke: "currentColor" })] }));
6
+ /** A camera — photo provenance on a vocab entry. */
7
+ export const IconCameraOutlineMedium = ({ size = 16, className }) => (_jsxs("svg", { width: size, height: size, className: className, viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", strokeWidth: 1.3, children: [_jsx("path", { d: "M2 5.2C2 4.65 2.45 4.2 3 4.2H4.8L5.7 2.6H10.3L11.2 4.2H13C13.55 4.2 14 4.65 14 5.2V12C14 12.55 13.55 13 13 13H3C2.45 13 2 12.55 2 12V5.2Z", stroke: "currentColor", strokeLinejoin: "round" }), _jsx("circle", { cx: "8", cy: "8.6", r: "2.5", stroke: "currentColor" })] }));
8
+ /** Spark cards — the vocab book. */
9
+ export const IconCardsOutlineMedium = ({ size = 16, className }) => (_jsxs("svg", { width: size, height: size, className: className, viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", strokeWidth: 1.3, children: [_jsx("rect", { x: "4.4", y: "4.4", width: "9.2", height: "9.2", rx: "1.4", stroke: "currentColor" }), _jsx("path", { d: "M11.6 4.4V3.6C11.6 2.85 11.05 2.3 10.3 2.3H3.6C2.85 2.3 2.3 2.85 2.3 3.6V10.3C2.3 11.05 2.85 11.6 3.6 11.6H4.4", stroke: "currentColor" })] }));
10
+ /** A pencil — editing a project. */
11
+ export const IconPenOutlineMedium = ({ size = 16, className }) => (_jsx("svg", { width: size, height: size, className: className, viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", strokeWidth: 1.3, children: _jsx("path", { d: "M2.5 13.5 3.2 10.6 10.8 3a1.4 1.4 0 0 1 2 0l.2.2a1.4 1.4 0 0 1 0 2L5.4 12.8 2.5 13.5ZM9.6 4.2l2.2 2.2", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round" }) }));
12
+ /** A page with lines — an article resource. */
13
+ export const IconArticleOutlineMedium = ({ size = 16, className }) => (_jsxs("svg", { width: size, height: size, className: className, viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", strokeWidth: 1.3, children: [_jsx("path", { d: "M4 1.8H9.6L12.5 4.7V14.2H4V1.8Z", stroke: "currentColor", strokeLinejoin: "round" }), _jsx("path", { d: "M9.4 2V4.9H12.4", stroke: "currentColor", strokeLinejoin: "round" }), _jsx("path", { d: "M6 8H10.5M6 10.6H10.5", stroke: "currentColor", strokeLinecap: "round" })] }));
14
+ /** A box — a repo resource. */
15
+ export const IconRepoOutlineMedium = ({ size = 16, className }) => (_jsxs("svg", { width: size, height: size, className: className, viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", strokeWidth: 1.3, children: [_jsx("path", { d: "M2.3 5.2 8 2.3 13.7 5.2V10.8L8 13.7 2.3 10.8V5.2Z", stroke: "currentColor", strokeLinejoin: "round" }), _jsx("path", { d: "M2.3 5.2 8 8.1 13.7 5.2M8 8.1V13.7", stroke: "currentColor", strokeLinejoin: "round" })] }));
16
+ /** A gear — a tool resource. */
17
+ export const IconToolOutlineMedium = ({ size = 16, className }) => (_jsxs("svg", { width: size, height: size, className: className, viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", strokeWidth: 1.3, children: [_jsx("circle", { cx: "8", cy: "8", r: "2.3", stroke: "currentColor" }), _jsx("path", { d: "M8 2.3V4.3M8 11.7V13.7M2.3 8H4.3M11.7 8H13.7M3.9 3.9 5.3 5.3M10.7 10.7 12.1 12.1M12.1 3.9 10.7 5.3M5.3 10.7 3.9 12.1", stroke: "currentColor", strokeLinecap: "round" })] }));
18
+ /** A graduation cap — a course resource. */
19
+ export const IconCourseOutlineMedium = ({ size = 16, className }) => (_jsxs("svg", { width: size, height: size, className: className, viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", strokeWidth: 1.3, children: [_jsx("path", { d: "M8 3.2 14.8 6.2 8 9.2 1.2 6.2 8 3.2Z", stroke: "currentColor", strokeLinejoin: "round" }), _jsx("path", { d: "M4.4 7.6V10.7C4.4 11.8 6 12.7 8 12.7 10 12.7 11.6 11.8 11.6 10.7V7.6", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round" }), _jsx("path", { d: "M14.8 6.2V9.8", stroke: "currentColor", strokeLinecap: "round" })] }));
20
+ /** A chip — a hardware resource. */
21
+ export const IconHardwareOutlineMedium = ({ size = 16, className }) => (_jsxs("svg", { width: size, height: size, className: className, viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", strokeWidth: 1.3, children: [_jsx("rect", { x: "4.4", y: "4.4", width: "7.2", height: "7.2", rx: "1.2", stroke: "currentColor" }), _jsx("path", { d: "M6.4 2V4.4M9.6 2V4.4M6.4 11.6V14M9.6 11.6V14M2 6.4H4.4M2 9.6H4.4M11.6 6.4H14M11.6 9.6H14", stroke: "currentColor", strokeLinecap: "round" })] }));
22
+ /** A speech bubble — ask the coach about a question. */
23
+ export const IconChatOutlineMedium = ({ size = 16, className }) => (_jsx("svg", { width: size, height: size, className: className, viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", strokeWidth: 1.3, children: _jsx("path", { d: "M3 3.5C3 2.9 3.4 2.5 4 2.5H12C12.6 2.5 13 2.9 13 3.5V9.5C13 10.1 12.6 10.5 12 10.5H7L4.5 13V10.5H4C3.4 10.5 3 10.1 3 9.5V3.5Z", stroke: "currentColor", strokeLinejoin: "round" }) }));
24
+ /** A flag — report a broken question. */
25
+ export const IconFlagOutlineMedium = ({ size = 16, className }) => (_jsx("svg", { width: size, height: size, className: className, viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", strokeWidth: 1.3, children: _jsx("path", { d: "M4 14V2.5M4 3H12L10.5 6L12 9H4", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round" }) }));
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Generated by scripts/sync-icon-artwork.mts from @deepseek-ai/dsh-client-ui-primitives
3
+ * (v0.2.1-rc.1, harness 1600d8a1e1) — do not edit by hand. The 0.1.5 and rc.1 host lines
4
+ * share no icon export name, so the rc.1 artwork is inlined here: pure SVG,
5
+ * no host runtime identity, identical on every line. Re-run the generator
6
+ * after adding an icon import or bumping the harness checkout.
7
+ *
8
+ * @module @khorsheed/dsh-library/client
9
+ */
10
+ /** Shared props for every product icon component (mirrors the upstream IconProps). */
11
+ export interface IconProps {
12
+ /** Square edge in px; defaults to the glyph's own drawn size. */
13
+ size?: number | undefined;
14
+ /** Extra class for layout placement; color rides currentColor. */
15
+ className?: string | undefined;
16
+ }
17
+ export declare const IconCheckOutlineMedium: ({ size, className }: IconProps) => import("react").JSX.Element;
18
+ export declare const IconChevronDownOutlineMedium: ({ size, className }: IconProps) => import("react").JSX.Element;
19
+ export declare const IconInfoOutlineMedium: ({ size, className }: IconProps) => import("react").JSX.Element;
20
+ export declare const IconLinkOutlineMedium: ({ size, className }: IconProps) => import("react").JSX.Element;
21
+ export declare const IconPlayOutlineMedium: ({ size, className }: IconProps) => import("react").JSX.Element;
22
+ export declare const IconPlusOutlineMedium: ({ size, className }: IconProps) => import("react").JSX.Element;
23
+ export declare const IconRefreshOutlineMedium: ({ size, className }: IconProps) => import("react").JSX.Element;
24
+ export declare const IconSearchOutlineMedium: ({ size, className }: IconProps) => import("react").JSX.Element;
25
+ export declare const IconSparkleMedium: ({ size, className }: IconProps) => import("react").JSX.Element;
26
+ export declare const IconTrashOutlineMedium: ({ size, className }: IconProps) => import("react").JSX.Element;
27
+ //# sourceMappingURL=icons.d.ts.map
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ export const IconCheckOutlineMedium = ({ size = 16, className }) => (_jsx("svg", { width: size, height: size, className: className, viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", strokeWidth: 1.3, children: _jsx("path", { d: "M2.25 8.5L5.49732 11.7473C5.90519 12.1552 6.57263 12.1344 6.95426 11.7018L13.75 4", stroke: "currentColor" }) }));
3
+ export const IconChevronDownOutlineMedium = ({ size = 14, className }) => (_jsx("svg", { width: size, height: size, className: className, viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", strokeWidth: 1.3, children: _jsx("path", { d: "M4 6L7.29289 9.29289C7.68342 9.68342 8.31658 9.68342 8.70711 9.29289L12 6", stroke: "currentColor" }) }));
4
+ export const IconInfoOutlineMedium = ({ size = 14, className }) => (_jsxs("svg", { width: size, height: size, className: className, viewBox: "0 0 14 14", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", strokeWidth: 1.3, children: [_jsx("path", { d: "M12.5757 7.00012C12.5757 3.92085 10.0794 1.42463 7.00012 1.42456C3.9208 1.42456 1.42456 3.9208 1.42456 7.00012C1.42463 10.0794 3.92085 12.5757 7.00012 12.5757C10.0793 12.5756 12.5756 10.0793 12.5757 7.00012ZM13.8002 7.00012C13.8001 10.7559 10.7559 13.8001 7.00012 13.8002C3.2443 13.8002 0.199291 10.7559 0.199219 7.00012C0.199219 3.24426 3.24426 0.199219 7.00012 0.199219C10.7559 0.199291 13.8002 3.2443 13.8002 7.00012Z", fill: "currentColor" }), _jsx("path", { d: "M7.6127 3.18921V4.55986H6.38735V3.18921H7.6127Z", fill: "currentColor" }), _jsx("path", { d: "M7.6127 5.68921V10.8109H6.38735V5.68921H7.6127Z", fill: "currentColor" })] }));
5
+ export const IconLinkOutlineMedium = ({ size = 16, className }) => (_jsxs("svg", { width: size, height: size, className: className, viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", strokeWidth: 1.3, children: [_jsx("path", { d: "M6.59961 9.40051C6.82779 9.6334 7.10015 9.81842 7.40074 9.94472C7.70132 10.071 8.02409 10.1361 8.35013 10.1361C8.67618 10.1361 8.99894 10.071 9.29953 9.94472C9.60011 9.81842 9.87247 9.6334 10.1007 9.40051L12.9015 6.59967C13.3658 6.13541 13.6266 5.50572 13.6266 4.84915C13.6266 4.19258 13.3658 3.56289 12.9015 3.09863C12.4372 2.63436 11.8075 2.37354 11.151 2.37354C10.4944 2.37354 9.86472 2.63436 9.40045 3.09863L9.05034 3.44873", stroke: "currentColor" }), _jsx("path", { d: "M9.40051 6.59959C9.17233 6.3667 8.89997 6.18169 8.59939 6.05538C8.2988 5.92907 7.97603 5.86401 7.64999 5.86401C7.32395 5.86401 7.00118 5.92907 6.70059 6.05538C6.40001 6.18169 6.12765 6.3667 5.89946 6.59959L3.09863 9.40043C2.63436 9.8647 2.37354 10.4944 2.37354 11.151C2.37354 11.8075 2.63436 12.4372 3.09863 12.9015C3.56289 13.3657 4.19258 13.6266 4.84915 13.6266C5.50572 13.6266 6.13541 13.3657 6.59967 12.9015L6.94978 12.5514", stroke: "currentColor" })] }));
6
+ export const IconPlayOutlineMedium = ({ size = 16, className }) => (_jsxs("svg", { width: size, height: size, className: className, viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", strokeWidth: 1.3, children: [_jsx("path", { d: "M8 14.5C11.5899 14.5 14.5 11.5899 14.5 8C14.5 4.41015 11.5899 1.5 8 1.5C4.41015 1.5 1.5 4.41015 1.5 8C1.5 11.5899 4.41015 14.5 8 14.5Z", stroke: "currentColor" }), _jsx("path", { d: "M10.3329 7.91346C10.3996 7.95195 10.3996 8.04818 10.3329 8.08667L6.78304 10.1362C6.71638 10.1747 6.63304 10.1266 6.63304 10.0496L6.63304 5.95055C6.63304 5.87357 6.71638 5.82546 6.78304 5.86395L10.3329 7.91346Z", stroke: "currentColor" })] }));
7
+ export const IconPlusOutlineMedium = ({ size = 16, className }) => (_jsxs("svg", { width: size, height: size, className: className, viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", strokeWidth: 1.3, children: [_jsx("path", { d: "M8 2V14", stroke: "currentColor" }), _jsx("path", { d: "M2 8H14", stroke: "currentColor" })] }));
8
+ export const IconRefreshOutlineMedium = ({ size = 16, className }) => (_jsxs("svg", { width: size, height: size, className: className, viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", strokeWidth: 1.3, children: [_jsx("path", { d: "M14.5001 8C14.5 9.28552 14.1188 10.5422 13.4045 11.611C12.6903 12.6799 11.6752 13.5129 10.4875 14.0049C9.29982 14.4968 7.99295 14.6255 6.73212 14.3747C5.4713 14.124 4.31314 13.505 3.4041 12.596C2.49514 11.687 1.87614 10.5288 1.62537 9.26798C1.37459 8.00716 1.50331 6.70028 1.99525 5.51261C2.48719 4.32494 3.32025 3.30981 4.3891 2.59557C5.45795 1.88134 6.71458 1.50008 8.0001 1.5C9.9001 1.5 11.7001 2.3 13.0001 3.6L14.5001 5.1", stroke: "currentColor" }), _jsx("path", { d: "M14.4999 1.5V5.1H10.8999", stroke: "currentColor" })] }));
9
+ export const IconSearchOutlineMedium = ({ size = 16, className }) => (_jsxs("svg", { width: size, height: size, className: className, viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", strokeWidth: 1.3, children: [_jsx("path", { d: "M6.58727 11.8586C9.55061 11.8586 11.9529 9.45637 11.9529 6.49304C11.9529 3.5297 9.55061 1.12744 6.58727 1.12744C3.62394 1.12744 1.22168 3.5297 1.22168 6.49304C1.22168 9.45637 3.62394 11.8586 6.58727 11.8586Z", stroke: "currentColor" }), _jsx("path", { d: "M10.2991 10.3933L14.7783 14.8725", stroke: "currentColor" })] }));
10
+ export const IconSparkleMedium = ({ size = 16, className }) => (_jsxs("svg", { width: size, height: size, className: className, viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", strokeWidth: 1.3, children: [_jsx("path", { d: "M5.875 3C5.875 6.33333 7.54167 8 10.875 8C7.54167 8 5.875 9.66667 5.875 13C5.875 9.66667 4.20833 8 0.875 8C4.20833 8 5.875 6.33333 5.875 3Z", stroke: "currentColor" }), _jsx("path", { d: "M12.375 1.55823C12.375 3.39156 13.2917 4.30823 15.125 4.30823C13.2917 4.30823 12.375 5.22489 12.375 7.05823C12.375 5.22489 11.4583 4.30823 9.625 4.30823C11.4583 4.30823 12.375 3.39156 12.375 1.55823Z", stroke: "currentColor" }), _jsx("path", { d: "M12.375 10.4418C12.375 11.7751 13.0417 12.4418 14.375 12.4418C13.0417 12.4418 12.375 13.1084 12.375 14.4418C12.375 13.1084 11.7083 12.4418 10.375 12.4418C11.7083 12.4418 12.375 11.7751 12.375 10.4418Z", stroke: "currentColor" })] }));
11
+ export const IconTrashOutlineMedium = ({ size = 16, className }) => (_jsxs("svg", { width: size, height: size, className: className, viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", strokeWidth: 1.3, children: [_jsx("path", { d: "M1.28149 3.88831H14.7187", stroke: "currentColor" }), _jsx("path", { d: "M5.41602 3.88833V2.47962C5.41602 2.29282 5.52492 2.11366 5.71876 1.98157C5.9126 1.84948 6.17551 1.77527 6.44964 1.77527H9.55053C9.82466 1.77527 10.0876 1.84948 10.2814 1.98157C10.4753 2.11366 10.5842 2.29282 10.5842 2.47962V3.88833", stroke: "currentColor" }), _jsx("path", { d: "M2.57349 3.88831L3.19366 13.2943C3.21937 13.5502 3.33952 13.7872 3.53065 13.9593C3.72178 14.1313 3.97016 14.2259 4.22729 14.2246H11.7728C12.0299 14.2259 12.2783 14.1313 12.4694 13.9593C12.6605 13.7872 12.7807 13.5502 12.8064 13.2943L13.4266 3.88831", stroke: "currentColor" }), _jsx("path", { d: "M6.44946 6.98926V11.1238", stroke: "currentColor" }), _jsx("path", { d: "M9.55054 6.98926V11.1238", stroke: "currentColor" })] }));
@@ -0,0 +1,35 @@
1
+ /**
2
+ * The library's browser half: mount the Remote, register the dictionaries and
3
+ * the right-Sidebar tab, and hand the pane the gestures it cannot perform for
4
+ * itself (priming the conversation, binding the session).
5
+ *
6
+ * Failure policy is the repo's "degrade, don't explode": genuinely required
7
+ * wiring (`slots`, `remote`, `locale`, `sidebarRightTabs`, `sessions`) is
8
+ * declared in `inject` and logged when it fails, while every optional surface
9
+ * is probed per gesture — a session without a conversation input makes the
10
+ * re-split gesture answer false instead of throwing.
11
+ *
12
+ * @module @khorsheed/dsh-library/client
13
+ */
14
+ import type { Context } from '@deepseek-ai/cordis';
15
+ import { LibraryPane } from './LibraryPane.tsx';
16
+ export { LibraryPane };
17
+ export { LIBRARY_KIND, LIBRARY_TAB_ID } from './definition.tsx';
18
+ /**
19
+ * Required services: the pane seat, the wire channel, the locale, the tab-type
20
+ * registry, and sessions (the re-split's conversation draft lives behind a
21
+ * session scope, and the tab type's preset-visibility criterion reads the
22
+ * current session). `remote.library` is deliberately NOT an inject: this
23
+ * plugin both mounts the namespace and consumes it — the canvas pattern (a
24
+ * declared inject would deadlock the loader).
25
+ */
26
+ export declare const inject: string[];
27
+ /**
28
+ * Client plugin body: mount the Remote, register the dictionaries, then the
29
+ * tab type (gated by the preset-visibility criterion) and its body.
30
+ *
31
+ * @param ctx - client root context.
32
+ * @returns the teardown for everything this body registered.
33
+ */
34
+ export declare function apply(ctx: Context): Promise<() => Promise<void>>;
35
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,231 @@
1
+ import libraryRemote from '@khorsheed/dsh-library/remote';
2
+ import { LIBRARY_KIND, LIBRARY_TAB_ID, libraryDefinition } from "./definition.js";
3
+ import { LibraryPane } from "./LibraryPane.js";
4
+ import { LibraryBindChip } from "./bind/BindChip.js";
5
+ import { createLibraryStore } from "./store.js";
6
+ import { RevealedProject } from "./reveal.js";
7
+ import { en, NS, zh } from "./locales.js";
8
+ import { LibraryTabVisibility, RegistrationToggle, } from "./preset-visibility.js";
9
+ export { LibraryPane };
10
+ export { LIBRARY_KIND, LIBRARY_TAB_ID } from "./definition.js";
11
+ /**
12
+ * Required services: the pane seat, the wire channel, the locale, the tab-type
13
+ * registry, and sessions (the re-split's conversation draft lives behind a
14
+ * session scope, and the tab type's preset-visibility criterion reads the
15
+ * current session). `remote.library` is deliberately NOT an inject: this
16
+ * plugin both mounts the namespace and consumes it — the canvas pattern (a
17
+ * declared inject would deadlock the loader).
18
+ */
19
+ export const inject = ['slots', 'remote', 'locale', 'sidebarRightTabs', 'sessions'];
20
+ function mainSessionId(list) {
21
+ const view = list;
22
+ return Object.values(view.byId).find(s => (s.retainedBy?.mainView ?? 0) > 0)?.id ?? view.current;
23
+ }
24
+ /**
25
+ * Client plugin body: mount the Remote, register the dictionaries, then the
26
+ * tab type (gated by the preset-visibility criterion) and its body.
27
+ *
28
+ * @param ctx - client root context.
29
+ * @returns the teardown for everything this body registered.
30
+ */
31
+ export async function apply(ctx) {
32
+ const disposers = [];
33
+ try {
34
+ disposers.push(await ctx.remote.$mount(libraryRemote));
35
+ }
36
+ catch (error) {
37
+ // A Remote already mounted by another composition fails loud; the rest of
38
+ // the plugin still registers, so the tab shows its empty state rather than
39
+ // disappearing.
40
+ ctx.logger.error(error);
41
+ }
42
+ ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'library: dictionaries');
43
+ const t = ctx.locale.bind(NS);
44
+ // Read lazily and through `ctx.get`: a composition without the host half
45
+ // yields undefined, and the pane's reads answer a refusal instead of
46
+ // pending forever on an inject it cannot satisfy.
47
+ const remote = ctx.get('remote.library');
48
+ /** The session's conversation input, when the composition has one (the reader's structural reach). */
49
+ const inputFor = (sessionId) => {
50
+ try {
51
+ const scope = ctx.get('sessions')?.scope(sessionId);
52
+ const input = scope?.get('conversation')
53
+ ?.input?.for(scope);
54
+ const face = input;
55
+ return typeof face?.setDraft === 'function' && typeof face.state?.getSnapshot === 'function'
56
+ ? face
57
+ : undefined;
58
+ }
59
+ catch {
60
+ return undefined;
61
+ }
62
+ };
63
+ /** The project the next reveal lands on (one per page; the pane takes it). */
64
+ const revealed = new RevealedProject();
65
+ /** The injected business face, identical for whichever session mounts it. */
66
+ const face = (sessionId) => ({
67
+ listProjects: () => remote.listProjects(),
68
+ getProject: id => remote.getProject({ id }),
69
+ createProject: request => remote.createProject(request),
70
+ updateProject: (projectId, patch) => remote.updateProject({ id: projectId, patch }),
71
+ deleteProject: projectId => remote.deleteProject({ id: projectId }),
72
+ removeResource: (projectId, resourceId) => remote.removeResource({ projectId, resourceId }),
73
+ moveResource: (projectId, resourceId, seq) => remote.moveResource({ projectId, resourceId, seq }),
74
+ setBookKind: (projectId, resourceId, bookKind) => remote.setBookKind({ projectId, resourceId, bookKind }),
75
+ getRoadmap: (projectId, resourceId) => remote.getRoadmap({ projectId, resourceId }),
76
+ adoptRoadmap: (projectId, resourceId) => remote.adoptRoadmap({ projectId, resourceId }),
77
+ discardDraft: (projectId, resourceId) => remote.discardDraft({ projectId, resourceId }),
78
+ markNodeRead: (projectId, resourceId, nodeId) => remote.markNodeRead(resourceId === undefined ? { projectId, nodeId } : { projectId, resourceId, nodeId }),
79
+ listPages: (projectId, resourceId, nodeId) => remote.listPages(nodeId === undefined ? { projectId, resourceId } : { projectId, resourceId, nodeId }),
80
+ readPagePhoto: (projectId, pageId) => remote.readPagePhoto({ projectId, pageId }),
81
+ listResources: projectId => remote.listResources({ projectId }),
82
+ listAttempts: (projectId, nodeId, assessmentId) => remote.listAttempts({ projectId, ...(nodeId === undefined ? {} : { nodeId }), ...(assessmentId === undefined ? {} : { assessmentId }) }),
83
+ listCards: query => remote.listCards(query),
84
+ dueCards: projectId => remote.dueCards(projectId === undefined ? {} : { projectId }),
85
+ editCard: (projectId, cardId, patch) => remote.editCard({ projectId, cardId, patch }),
86
+ undoCards: (projectId, ids, at) => remote.undoCards({ projectId, ids, at }),
87
+ reviewCard: (projectId, cardId, rating) => remote.reviewCard({ projectId, cardId, rating }),
88
+ listAssessments: (projectId, resourceId, nodeId, type) => remote.listAssessments({ projectId, ...(resourceId === undefined ? {} : { resourceId }), ...(nodeId === undefined ? {} : { nodeId }), ...(type === undefined ? {} : { type }) }),
89
+ getAssessment: (projectId, assessmentId) => remote.getAssessment({ projectId, assessmentId }),
90
+ putAttempt: (projectId, assessmentId, verdicts) => remote.putAttempt({ projectId, assessmentId, verdicts }),
91
+ recentRecords: () => remote.recentRecords({ limit: 3 }),
92
+ recentChanges: () => remote.recentChanges({ limit: 5 }),
93
+ createRetake: (projectId, resourceId, nodeId, keys) => remote.createRetake({ projectId, ...(resourceId === undefined ? {} : { resourceId }), ...(nodeId === undefined ? {} : { nodeId }), ...(keys === undefined ? {} : { keys }) }),
94
+ latestActivity: () => remote.latestActivity(),
95
+ boundTarget: () => remote.boundTarget(sessionId),
96
+ bindTarget: binding => remote.bindTarget(sessionId, { binding }),
97
+ revealed,
98
+ sendMessage: text => {
99
+ const input = inputFor(sessionId);
100
+ if (input?.submit === undefined)
101
+ return false;
102
+ input.setDraft(text);
103
+ input.submit('queue');
104
+ return true;
105
+ },
106
+ sendPhoto: (file, text) => {
107
+ const input = inputFor(sessionId);
108
+ const conversation = ctx.get('sessions')?.scope(sessionId)?.get('conversation');
109
+ if (input?.submit === undefined || input.addAttachments === undefined || conversation?.createDrafts === undefined)
110
+ return false;
111
+ try {
112
+ const drafts = conversation.createDrafts(sessionId, [file]);
113
+ input.setDraft(text);
114
+ if (!input.addAttachments(drafts.map(draft => draft.id))) {
115
+ conversation.releaseDraftAttachments?.(drafts);
116
+ return false;
117
+ }
118
+ input.submit('queue');
119
+ return true;
120
+ }
121
+ catch {
122
+ return false;
123
+ }
124
+ },
125
+ primeConversation: text => {
126
+ const input = inputFor(sessionId);
127
+ if (input === undefined)
128
+ return false;
129
+ const current = input.state.getSnapshot().draft;
130
+ input.setDraft(current.trim() === '' ? text : `${current}\n\n${text}`);
131
+ return true;
132
+ },
133
+ });
134
+ // Stage one of the right-Sidebar registration: the page type itself (guide
135
+ // entry, no address claims). The type registers exactly while the current
136
+ // session can reach the library tools — a root-mounted tool row (the
137
+ // community default) OR the session's preset composition naming it (the
138
+ // learning-mode recipe); every unreadable path fails open. Hidden means NOT
139
+ // registered: the guide enumerates the registry, opened tabs are stored per
140
+ // session, and an unregistered kind renders the host's tab.unavailable
141
+ // fallback.
142
+ const pluginInventory = ctx.get('remote.pluginInventory');
143
+ const tabVisibility = new LibraryTabVisibility(ctx, pluginInventory);
144
+ ctx.effect(() => {
145
+ const toggle = new RegistrationToggle(() => ctx.sidebarRightTabs.register(libraryDefinition(t)), () => tabVisibility.show(mainSessionId(ctx.sessions.list.getSnapshot())));
146
+ toggle.setReady(true);
147
+ const unsubscribe = tabVisibility.subscribe(() => { toggle.sync(); });
148
+ return () => { unsubscribe(); toggle.setReady(false); };
149
+ }, 'library: tab type visibility');
150
+ // Reveal the library beside the conversation that just started using it: a
151
+ // library tool from the on-screen session wrote (a project created, a book
152
+ // shelved, a path drafted) → open the 图书馆 tab on the session's bound
153
+ // project (the write bound it), once per session per page load, so closing
154
+ // it again sticks. The first read is the baseline (writes
155
+ // from before this page loaded never open anything). Degrades to nothing
156
+ // without the sidebar surface or the host half.
157
+ ctx.effect(() => {
158
+ const revealedSessions = new Set();
159
+ let baseline;
160
+ let primed = false;
161
+ const poll = () => {
162
+ if (remote === undefined || document.visibilityState !== 'visible')
163
+ return;
164
+ void remote.latestActivity().then((result) => {
165
+ if (!result.ok)
166
+ return;
167
+ const write = result.value.session;
168
+ if (!primed) {
169
+ primed = true;
170
+ baseline = write?.at;
171
+ return;
172
+ }
173
+ if (write === undefined || (baseline !== undefined && write.at <= baseline))
174
+ return;
175
+ baseline = write.at;
176
+ const main = mainSessionId(ctx.sessions.list.getSnapshot());
177
+ if (main === undefined || String(main) !== write.id || revealedSessions.has(write.id) || !tabVisibility.show(main))
178
+ return;
179
+ revealedSessions.add(write.id);
180
+ const open = () => {
181
+ try {
182
+ ctx.get('sidebarRight')?.openTab?.(LIBRARY_KIND);
183
+ }
184
+ catch {
185
+ // No on-screen sidebar surface: nothing to reveal.
186
+ }
187
+ };
188
+ // Land on the project the write bound; without a binding the tab opens on today.
189
+ void remote.boundTarget(main).then((bound) => {
190
+ if (bound.ok && bound.value !== null)
191
+ revealed.reveal(bound.value.projectId);
192
+ }, () => { }).finally(open);
193
+ }, () => { });
194
+ };
195
+ poll();
196
+ const timer = setInterval(poll, 5000);
197
+ return () => { clearInterval(timer); };
198
+ }, 'library: reveal on first write');
199
+ // Stage two: the tab body, under this package's id in the keyed pane seat.
200
+ ctx.effect(() => ctx.slots.inject('sidebar.right.pane.tab', () => ctx.slots.register({
201
+ name: 'sidebar.right.pane.tab',
202
+ key: LIBRARY_TAB_ID,
203
+ locale: NS,
204
+ store: createLibraryStore,
205
+ inject: face,
206
+ }, LibraryPane)), 'library: sidebar tab body');
207
+ // The session header's library chip: shown only in sessions that hold the
208
+ // library tools (the strict half of the criterion). The header seat is
209
+ // declared by ui-conversation, which this package does not depend on —
210
+ // without it the inject never fires.
211
+ const headerSlots = ctx.slots;
212
+ const bindFace = (sessionId) => ({
213
+ offered: () => tabVisibility.grants(sessionId),
214
+ subscribe: listener => tabVisibility.subscribe(listener),
215
+ boundTarget: () => remote.boundTarget(sessionId),
216
+ bindTarget: binding => remote.bindTarget(sessionId, { binding }),
217
+ listProjects: () => remote.listProjects(),
218
+ });
219
+ ctx.effect(() => headerSlots.inject('conversation.session.header.actions', () => headerSlots.register({
220
+ name: 'conversation.session.header.actions',
221
+ id: 'library-bind',
222
+ // After room's invite (30) and canvas's work chip (40).
223
+ order: 50,
224
+ locale: NS,
225
+ inject: bindFace,
226
+ }, LibraryBindChip)), 'library: session binding chip');
227
+ return async () => {
228
+ for (const dispose of disposers.reverse())
229
+ await dispose();
230
+ };
231
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Judging the answers the sidebar can judge itself: a choice against its key
3
+ * (a letter, the option text, or a lettered option) and a fill-in against its
4
+ * key — the same text loosely (width, case, spaces, LaTeX vs unicode, the
5
+ * comma's width), or the same NUMBER (1/2 = 0.5 = \frac{1}{2}), part by part
6
+ * for a multi-blank answer; alternatives are written " / ", "|" or "或". What
7
+ * still disagrees, the learner may overrule (「我答对了」). Open answers are
8
+ * the coach's.
9
+ *
10
+ * @module @khorsheed/dsh-library/client/judge
11
+ */
12
+ /**
13
+ * The value of a plain numeric expression — digits, + - × / ^, parentheses,
14
+ * √, π, a trailing % — or undefined. A tiny recursive-descent parser, never
15
+ * eval: 1/2, 0.5, √3/2, 2π/3 and 50% all read as numbers.
16
+ */
17
+ export declare function numericValue(text: string): number | undefined;
18
+ /** The option index an answer key names, or -1. */
19
+ export declare function choiceIndex(options: readonly string[], answer: string): number;
20
+ /**
21
+ * Whether a fill-in answer matches the key. The whole key is tried first —
22
+ * "1/2" is a fraction, never two alternatives — then alternatives written
23
+ * " / " (spaced), "|" or "或".
24
+ */
25
+ export declare function fillMatches(input: string, answer: string): boolean;
26
+ /**
27
+ * A fill-in judged by the project's profile: 'math' by text or value
28
+ * (fillMatches); 'spelling' also forgives a UK/US spelling or a plural.
29
+ */
30
+ export declare function judgeFill(fill: 'math' | 'spelling', input: string, answer: string): boolean;
31
+ /** How a due card is asked. */
32
+ export type ReviewMode = 'cloze' | 'choice' | 'spell' | 'explain';
33
+ /** The slice of a card the review needs. */
34
+ export interface ReviewCardLike {
35
+ readonly id: string;
36
+ readonly kind: 'word' | 'concept';
37
+ readonly term: string;
38
+ readonly aliases: readonly string[];
39
+ readonly gloss: string;
40
+ readonly contexts: readonly {
41
+ readonly sentence: string;
42
+ }[];
43
+ readonly mastery: {
44
+ readonly box: number;
45
+ readonly streak: number;
46
+ };
47
+ }
48
+ /** The sentence with the term blanked, or undefined when the sentence does not hold it. */
49
+ export declare function clozeOf(sentence: string, term: string, aliases?: readonly string[]): {
50
+ before: string;
51
+ after: string;
52
+ answer: string;
53
+ } | undefined;
54
+ /**
55
+ * Pick how to ask a card: a concept is explained; a word the learner already
56
+ * knows well (box 3+) is spelled; otherwise a word met in a sentence is a
57
+ * cloze or a meaning choice, alternating by its streak (the choice needs three
58
+ * other glosses for distractors).
59
+ */
60
+ export declare function pickMode(card: ReviewCardLike, otherGlosses: number): ReviewMode;
61
+ /** Whether a typed word is the term (case, width, a UK/US spelling and a plural apart). */
62
+ export declare function spellMatches(input: string, answers: readonly string[]): boolean;
63
+ /** Per-letter marks for a spelling attempt: true where the letter matches the target at that position. */
64
+ export declare function letterMarks(input: string, target: string): boolean[];
65
+ /** Three distractors from other cards' glosses, shuffled with the answer; `random` is injectable for tests. */
66
+ export declare function choiceOptions(answer: string, others: readonly string[], random?: () => number): {
67
+ options: string[];
68
+ correct: number;
69
+ };
70
+ //# sourceMappingURL=judge.d.ts.map