dsh-plugin-lookatstudy 0.9.0 → 0.10.0

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.
package/lib/client.js CHANGED
@@ -41,9 +41,15 @@ window.__ModuleLoader__.load({
41
41
  .lks-body{flex:1;min-height:0;min-width:0;display:flex;gap:16px}
42
42
  .lks-col{display:flex;flex-direction:column;min-width:0}
43
43
  .lks-colhead{flex:none;font-size:12px;font-weight:600;color:var(--dsw-alias-label-tertiary);letter-spacing:.06em;text-transform:uppercase;margin-bottom:10px}
44
- .lks-col-rail{flex:1 1 0;min-width:190px;overflow-y:auto;padding:2px 6px}
44
+ .lks-col-rail{flex:0 1 260px;min-width:180px;overflow-y:auto;padding:2px 6px}
45
45
  .lks-col-tutor{flex:0 0 auto;width:min(100%,var(--dsh-composer-card-max-width,780px))}
46
46
  .lks-col-bb{flex:1 1 0;min-width:230px;overflow-y:auto;padding:2px 6px}
47
+ /* While the study view is mounted in wide mode, the host composer card is
48
+ shifted under the tutor column (the transcript's width axis) instead of the
49
+ scroll body's center — views.tsx measures the offset, sets the variable and
50
+ toggles the class, and removes both whenever the shift collapses (narrow
51
+ pane, hidden view) so the host's natural centering comes back. */
52
+ [data-composer-card].lks-composer-follow{align-self:flex-start;margin-left:var(--lks-composer-shift,0px)}
47
53
  /* narrow (<1220px): one composer-width pane at a time, chosen by a centered
48
54
  segmented pill group (joined buttons in one capsule — button language, not
49
55
  tab language, against the host's view tabs right above). */
@@ -72,7 +78,7 @@ window.__ModuleLoader__.load({
72
78
  .lks-masterybar i{display:block;height:100%;background:var(--dsw-alias-state-business-primary);transition:width .3s ease}
73
79
  .lks-masterybar.gold i{background:var(--dsw-alias-state-warn-primary)}
74
80
  .lks-search{width:100%;box-sizing:border-box;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;color:var(--dsw-alias-label-primary);font-family:inherit;font-size:13.5px;padding:5px 9px;margin-bottom:4px}
75
- .lks-search:focus{outline:none;border-color:var(--dsw-alias-state-business-primary)}
81
+ .lks-search:focus{outline:none;border-color:var(--dsw-alias-state-business-primary);box-shadow:0 0 0 2px var(--dsw-alias-border-l3)}
76
82
  .lks-sec-num{display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;border-radius:50%;background:var(--dsw-alias-state-warn-tertiary);color:var(--dsw-alias-state-warn-label);font-size:11px;font-weight:700;margin-right:4px}
77
83
  .lks-tag.due{background:var(--dsw-alias-state-warn-tertiary);color:var(--dsw-alias-state-warn-label)}
78
84
  .lks-import{margin-top:8px}
@@ -87,13 +93,25 @@ window.__ModuleLoader__.load({
87
93
  .lks-duebox .lks-over{color:var(--dsw-alias-state-warn-label);flex:none}
88
94
 
89
95
  .lks-sec{font-size:12px;color:var(--dsw-alias-label-tertiary);text-transform:uppercase;letter-spacing:.05em;margin:14px 0 4px}
90
- .lks-node{display:flex;align-items:center;gap:8px;padding:6px 9px;border-radius:8px;margin:1px 0;cursor:pointer}
96
+ /* Collapsible section head: a real button (keyboard can toggle it), sticky so
97
+ the current chapter stays identified while 4000px of nodes scroll under it. */
98
+ .lks-sechead{display:flex;align-items:center;gap:6px;width:100%;box-sizing:border-box;font-size:12px;font-weight:600;color:var(--dsw-alias-label-tertiary);text-transform:uppercase;letter-spacing:.05em;text-align:left;padding:6px 4px;margin:14px 0 4px;border-radius:6px;position:sticky;top:0;z-index:1;background:var(--dsw-alias-bg-base)}
99
+ .lks-sechead:hover{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover)}
100
+ .lks-sechead:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-border-l3);outline:none}
101
+ .lks-sechead-t{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
102
+ .lks-sechead-c{flex:none;font-size:10px}
103
+ .lks-sechead-n{flex:none;font-size:11px;text-transform:none;letter-spacing:0}
104
+ .lks-node{display:flex;align-items:center;gap:8px;width:100%;text-align:left;padding:6px 9px;border-radius:8px;margin:1px 0;cursor:pointer}
91
105
  .lks-node:hover{background:var(--dsw-alias-interactive-bg-hover)}
106
+ .lks-node:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-border-l3);outline:none}
92
107
  .lks-node.focus{background:var(--dsw-alias-bg-layer-3);outline:1px solid var(--dsw-alias-border-l2)}
93
108
  .lks-node .lks-g{width:18px;text-align:center;flex:none}
94
109
  .lks-node .lks-t{flex:1;font-size:14px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
95
- .lks-node.locked{opacity:.45;cursor:not-allowed}
96
- .lks-node.locked:hover{background:none}
110
+ /* aria-disabled (not the disabled attribute): the row stays focusable so the
111
+ status tooltip can explain WHY it is locked — Firefox drops titles on truly
112
+ disabled form controls. */
113
+ .lks-node[aria-disabled='true']{opacity:.45;cursor:not-allowed}
114
+ .lks-node[aria-disabled='true']:hover{background:none}
97
115
  .lks-tag{font-size:11px;border-radius:4px;padding:0 4px;flex:none}
98
116
  .lks-tag.weak{background:var(--dsw-alias-state-warn-tertiary);color:var(--dsw-alias-state-warn-label)}
99
117
  .lks-tag.fric{background:var(--dsw-alias-state-error-primary);color:#fff}
@@ -141,7 +159,9 @@ window.__ModuleLoader__.load({
141
159
  .lks-starter:hover{border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-label-primary)}
142
160
 
143
161
  /* proposal banner + shared buttons */
144
- .lks-banner{display:flex;align-items:center;gap:10px;background:var(--dsw-alias-state-warn-tertiary);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;padding:10px 14px;margin:8px 0;font-size:14px;flex:none}
162
+ /* A mastery proposal is a positive, informational moment — business tint, not
163
+ the warn tint it previously wore (a reviewer flagged the semantic mismatch). */
164
+ .lks-banner{display:flex;align-items:center;gap:10px;background:var(--dsw-alias-state-business-tertiary);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;padding:10px 14px;margin:8px 0;font-size:14px;flex:none}
145
165
  .lks-banner .lks-why{flex:1;color:var(--dsw-alias-label-secondary)}
146
166
  .lks-btn{display:inline-flex;align-items:center;gap:6px;border-radius:8px;padding:6px 14px;font-size:13.5px;font-weight:600;flex:none}
147
167
  .lks-btn.primary{background:var(--dsw-alias-state-business-primary);color:#fff}
@@ -176,7 +196,7 @@ window.__ModuleLoader__.load({
176
196
  .lks-zone{margin-bottom:14px}
177
197
  .lks-zone h4{font-size:14px;color:var(--dsw-alias-label-secondary);margin:0 0 8px;font-weight:600}
178
198
  .lks-note{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);border-radius:10px;padding:10px 14px;margin-bottom:8px}
179
- .lks-note .lks-note-src{float:right;font-size:10px;color:var(--dsw-alias-label-tertiary)}
199
+ .lks-note .lks-note-src{float:right;font-size:11.5px;color:var(--dsw-alias-label-secondary)}
180
200
  .lks-note .lks-note-title{font-weight:600;font-size:13px}
181
201
  .lks-note .lks-note-text{margin-top:4px;white-space:pre-wrap;font-size:13px;color:var(--dsw-alias-label-secondary)}
182
202
  .lks-note .lks-note-q{margin-top:6px;color:var(--dsw-alias-label-tertiary);font-size:12px;border-left:2px solid var(--dsw-alias-state-warn-primary);padding-left:8px}
@@ -573,6 +593,17 @@ window.__ModuleLoader__.load({
573
593
  function titleMatches(title, query) {
574
594
  return query.trim().toLowerCase().split(/\s+/).filter(Boolean).every((k) => title.toLowerCase().includes(k));
575
595
  }
596
+ /**
597
+ * Default expansion for one rail section: collapsed when every study lesson is
598
+ * done (mastered) or not yet reachable (locked) — long courses otherwise scroll
599
+ * 5× their viewport. The active frontier and the focus lesson's section stay
600
+ * open; exam nodes never force a section open (they are gated separately).
601
+ * Pure.
602
+ * @param section - one course section projection with lesson kinds/statuses.
603
+ */
604
+ function sectionDefaultOpen(section) {
605
+ return section.lessons.some((l) => l.focus || l.kind !== "exam" && l.status !== "mastered" && l.status !== "locked");
606
+ }
576
607
  /** The rail's import row: a GitHub URL input plus the paste/folder hints. */
577
608
  function ImportRow({ send }) {
578
609
  const [url, setUrl] = (0, react.useState)("");
@@ -776,6 +807,42 @@ window.__ModuleLoader__.load({
776
807
  const { data, activate, setMode, setFocus, deleteCourse, bindLessonSession } = useStudy();
777
808
  const snapshot = useSession((s) => s);
778
809
  const [pane, setPane] = (0, react.useState)(storedPane);
810
+ const rootRef = (0, react.useRef)(null);
811
+ (0, react.useEffect)(() => {
812
+ const root = rootRef.current;
813
+ const card = document.querySelector("[data-composer-card]");
814
+ const seat = card?.parentElement ?? null;
815
+ if (root === null || card === null || seat === null) return;
816
+ const clear = () => {
817
+ card.classList.remove("lks-composer-follow");
818
+ card.style.removeProperty("--lks-composer-shift");
819
+ };
820
+ const apply = () => {
821
+ const tutor = root.querySelector(".lks-col-tutor");
822
+ const rail = root.querySelector(".lks-col-rail");
823
+ if (tutor === null || rail === null || getComputedStyle(rail).display === "none") return clear();
824
+ const seatRect = seat.getBoundingClientRect();
825
+ const tutorRect = tutor.getBoundingClientRect();
826
+ if (seatRect.width === 0 || tutorRect.width === 0) return clear();
827
+ const pad = parseFloat(getComputedStyle(seat).paddingLeft) || 0;
828
+ const cardW = card.getBoundingClientRect().width;
829
+ const max = seatRect.width - 2 * pad - cardW;
830
+ const shift = Math.round(Math.max(0, Math.min(tutorRect.left - (seatRect.left + pad), max)));
831
+ if (shift < 2) return clear();
832
+ card.style.setProperty("--lks-composer-shift", `${shift}px`);
833
+ card.classList.add("lks-composer-follow");
834
+ };
835
+ apply();
836
+ const ro = new ResizeObserver(apply);
837
+ ro.observe(root);
838
+ ro.observe(seat);
839
+ window.addEventListener("resize", apply);
840
+ return () => {
841
+ ro.disconnect();
842
+ window.removeEventListener("resize", apply);
843
+ clear();
844
+ };
845
+ }, []);
779
846
  const send = (text) => {
780
847
  (async () => {
781
848
  if (data?.active !== true) await activate(true);
@@ -784,6 +851,7 @@ window.__ModuleLoader__.load({
784
851
  })();
785
852
  };
786
853
  return (0, react.createElement)("div", {
854
+ ref: rootRef,
787
855
  className: "lks-root lks-study",
788
856
  "data-conversation-composer-overlay": "",
789
857
  "data-pane": pane
@@ -876,6 +944,8 @@ window.__ModuleLoader__.load({
876
944
  const [query, setQuery] = (0, react.useState)("");
877
945
  const [confirmDelete, setConfirmDelete] = (0, react.useState)(false);
878
946
  const [showImport, setShowImport] = (0, react.useState)(false);
947
+ /** Per-section open overrides (courseId/sectionTitle → open?), on top of the frontier default. */
948
+ const [secOpen, setSecOpen] = (0, react.useState)({});
879
949
  const [error, setError] = (0, react.useState)(null);
880
950
  const reportError = (err) => {
881
951
  setError(err instanceof Error ? err.message : String(err));
@@ -933,7 +1003,29 @@ window.__ModuleLoader__.load({
933
1003
  }
934
1004
  }
935
1005
  }
936
- }), data.dueCount > 0 ? (0, react.createElement)("div", { className: "lks-duebox" }, `🔁 待复习 ${data.dueCount}`, ...data.due.map((d) => (0, react.createElement)("div", {
1006
+ }), course.sections.some((s) => s.lessons.some((l) => l.focus)) ? (0, react.createElement)("button", {
1007
+ className: "lks-btn ghost",
1008
+ style: {
1009
+ margin: "0 0 6px",
1010
+ padding: "3px 8px",
1011
+ fontSize: "13px"
1012
+ },
1013
+ title: "在课程树中定位当前焦点课时(自动展开所在章节)",
1014
+ onClick: () => {
1015
+ const focusSection = course.sections.find((s) => s.lessons.some((l) => l.focus));
1016
+ if (focusSection === void 0) return;
1017
+ setSecOpen((m) => ({
1018
+ ...m,
1019
+ [`${courseId}/${focusSection.title}`]: true
1020
+ }));
1021
+ window.setTimeout(() => {
1022
+ document.querySelector(".lks-col-rail .lks-node.focus")?.scrollIntoView({
1023
+ block: "center",
1024
+ behavior: "smooth"
1025
+ });
1026
+ }, 60);
1027
+ }
1028
+ }, "📍 回到当前课时") : null, data.dueCount > 0 ? (0, react.createElement)("div", { className: "lks-duebox" }, `🔁 待复习 ${data.dueCount}`, ...data.due.map((d) => (0, react.createElement)("div", {
937
1029
  key: d.lessonId,
938
1030
  className: "lks-due-item"
939
1031
  }, (0, react.createElement)("span", null, d.lessonTitle), d.overdueDays > 0 ? (0, react.createElement)("span", { className: "lks-over" }, `超${d.overdueDays}天`) : null)), (0, react.createElement)("button", {
@@ -946,14 +1038,27 @@ window.__ModuleLoader__.load({
946
1038
  const examAllowed = examOpen(section.lessons);
947
1039
  const lessons = section.lessons.filter((l) => query.trim() === "" || titleMatches(l.title, query) || l.focus);
948
1040
  if (lessons.length === 0) return [];
949
- return [(0, react.createElement)("div", {
1041
+ const secKey = `${courseId}/${section.title}`;
1042
+ const open = query.trim() !== "" || (secOpen[secKey] ?? sectionDefaultOpen(section));
1043
+ return [(0, react.createElement)("button", {
950
1044
  key: section.title,
951
- className: "lks-sec"
952
- }, (0, react.createElement)("span", { className: "lks-sec-num" }, String(section.index + 1)), ` ${section.title}`), ...lessons.map((lesson) => {
1045
+ type: "button",
1046
+ className: "lks-sechead",
1047
+ "aria-expanded": String(open),
1048
+ title: open ? "折叠本章节" : `展开本章节(${section.lessons.length} 课时)`,
1049
+ onClick: () => {
1050
+ setSecOpen((m) => ({
1051
+ ...m,
1052
+ [secKey]: !open
1053
+ }));
1054
+ }
1055
+ }, (0, react.createElement)("span", { className: "lks-sec-num" }, String(section.index + 1)), (0, react.createElement)("span", { className: "lks-sechead-t" }, section.title), open ? null : (0, react.createElement)("span", { className: "lks-sechead-n" }, `${section.lessons.length} 课`), (0, react.createElement)("span", { className: "lks-sechead-c" }, open ? "▾" : "▸")), ...open ? lessons.map((lesson) => {
953
1056
  const locked = lesson.status === "locked" || lesson.kind === "exam" && !examAllowed;
954
- return (0, react.createElement)("div", {
1057
+ return (0, react.createElement)("button", {
955
1058
  key: lesson.id,
956
- className: `lks-node${locked ? " locked" : ""}${lesson.focus ? " focus" : ""}`,
1059
+ type: "button",
1060
+ className: `lks-node${lesson.focus ? " focus" : ""}`,
1061
+ "aria-disabled": locked || void 0,
957
1062
  title: jumping === lesson.id ? "正在打开课时会话…" : statusTitle(lesson.kind, locked ? "locked" : lesson.status),
958
1063
  onClick: () => {
959
1064
  if (locked) return;
@@ -980,7 +1085,7 @@ window.__ModuleLoader__.load({
980
1085
  className: "lks-pct",
981
1086
  title: "课时掌握度 = 最薄弱知识点的掌握度"
982
1087
  }, `${lesson.masteryPct}%`) : null);
983
- })];
1088
+ }) : []];
984
1089
  }), (0, react.createElement)("button", {
985
1090
  className: "lks-btn ghost",
986
1091
  style: { marginTop: "10px" },
package/lib/client.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":["createElement","useSyncExternalStore","useState","createElement","useRef","createElement","useState"],"sources":["../src/client/styles.ts","../src/client/icons.tsx","../src/client/data.ts","../src/markdown.ts","../src/client/views.tsx","../src/client/starter.tsx","../src/client/index.ts"],"sourcesContent":["/**\n * Style injection for the study tab: one `<style>` element of `.lks-*`\n * classes authored against the dsh `--dsw-*` design tokens, so the tab\n * follows the app's theme (light/dark) instead of carrying its own palette.\n * CSS Modules are unavailable to patch-layer bundles, so the stylesheet is\n * injected once at client-plugin apply; the `data-` attribute keeps the\n * injection idempotent.\n * @module dsh-plugin-lookatstudy/client/styles\n */\n\n/** Marker attribute proving the stylesheet is already in the document. */\nconst STYLE_ID = 'data-dsh-plugin-lookatstudy'\n\n/**\n * The study tab's stylesheet. Class names are `lks-*` namespaced; colors,\n * fonts, and radii come from `--dsw-*` tokens wherever the concept exists.\n */\nconst CSS = `\n.lks-root{font-family:var(--dsw-font-family)}\n.lks-root :where(button){font-family:var(--dsw-font-family);cursor:pointer;border:none;background:none;padding:0}\n.lks-root :where(a){color:var(--dsw-alias-state-business-primary)}\n\n/* ── the study tab: composer-width center, self-scrolling flanks ──\n data-conversation-composer-overlay (set on the root element) opts the\n view into the host's overlay mode: viewArea becomes a fixed-height\n non-growing host and the composer floats over the tab's bottom band, so\n the tab itself never feeds the page scroll. The bottom padding clears the\n floating composer via the host-published live --dsh-composer-height. The\n center strip reuses the conversation column's width axis\n (--dsh-composer-card-max-width inherits from ConversationRoot's root), so\n the tutor transcript is exactly as wide as the native composer; the\n flanking columns absorb the remaining width and scroll independently. */\n.lks-study{height:100%;min-height:0;width:100%;box-sizing:border-box;overflow:hidden;display:flex;flex-direction:column;padding:10px 14px calc(var(--dsh-composer-height, 152px) + 12px);color:var(--dsw-alias-label-primary);container:lksstudy/inline-size}\n/* Direction carrier (a container query cannot style the container itself). */\n.lks-body{flex:1;min-height:0;min-width:0;display:flex;gap:16px}\n.lks-col{display:flex;flex-direction:column;min-width:0}\n.lks-colhead{flex:none;font-size:12px;font-weight:600;color:var(--dsw-alias-label-tertiary);letter-spacing:.06em;text-transform:uppercase;margin-bottom:10px}\n.lks-col-rail{flex:1 1 0;min-width:190px;overflow-y:auto;padding:2px 6px}\n.lks-col-tutor{flex:0 0 auto;width:min(100%,var(--dsh-composer-card-max-width,780px))}\n.lks-col-bb{flex:1 1 0;min-width:230px;overflow-y:auto;padding:2px 6px}\n/* narrow (<1220px): one composer-width pane at a time, chosen by a centered\n segmented pill group (joined buttons in one capsule — button language, not\n tab language, against the host's view tabs right above). */\n.lks-switch{display:none;flex-direction:row;gap:2px;align-self:center;margin-bottom:10px;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:999px;padding:3px}\n.lks-switch-btn{border-radius:999px;padding:5px 18px;font-size:14px;color:var(--dsw-alias-label-tertiary);background:transparent;transition:background .12s ease,color .12s ease}\n.lks-switch-btn:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}\n.lks-switch-btn.on{background:var(--dsw-alias-state-business-primary);color:#fff;font-weight:600}\n@container lksstudy (max-width: 1220px){\n .lks-body{flex-direction:column}\n .lks-switch{display:flex}\n .lks-colhead{display:none}\n .lks-col{flex:1 1 auto;min-height:0;width:auto;overflow-y:auto}\n .lks-col-tutor{width:min(100%,var(--dsh-composer-card-max-width,780px));overflow:hidden;display:flex;flex-direction:column}\n .lks-study[data-pane='rail'] .lks-col-tutor,.lks-study[data-pane='rail'] .lks-col-bb,\n .lks-study[data-pane='tutor'] .lks-col-rail,.lks-study[data-pane='tutor'] .lks-col-bb,\n .lks-study[data-pane='bb'] .lks-col-rail,.lks-study[data-pane='bb'] .lks-col-tutor{display:none}\n}\n\n/* ── left column: course rail ── */\n.lks-rail-head{display:flex;align-items:center;gap:6px;margin-bottom:4px}\n.lks-rail-head .lks-rail-select{flex:1;min-width:0;margin-bottom:0}\n.lks-rail-head .lks-btn{padding:3px 8px;font-size:13px}\n.lks-rail-title{font-size:16px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.lks-rail-sub{font-size:13px;color:var(--dsw-alias-label-tertiary);margin:4px 0 6px}\n.lks-masterybar{height:5px;border-radius:3px;background:var(--dsw-alias-bg-layer-3);overflow:hidden;margin-bottom:8px}\n.lks-masterybar i{display:block;height:100%;background:var(--dsw-alias-state-business-primary);transition:width .3s ease}\n.lks-masterybar.gold i{background:var(--dsw-alias-state-warn-primary)}\n.lks-search{width:100%;box-sizing:border-box;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;color:var(--dsw-alias-label-primary);font-family:inherit;font-size:13.5px;padding:5px 9px;margin-bottom:4px}\n.lks-search:focus{outline:none;border-color:var(--dsw-alias-state-business-primary)}\n.lks-sec-num{display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;border-radius:50%;background:var(--dsw-alias-state-warn-tertiary);color:var(--dsw-alias-state-warn-label);font-size:11px;font-weight:700;margin-right:4px}\n.lks-tag.due{background:var(--dsw-alias-state-warn-tertiary);color:var(--dsw-alias-state-warn-label)}\n.lks-import{margin-top:8px}\n.lks-import .lks-inputrow{margin-top:0}\n.lks-inputrow{display:flex;gap:8px;align-items:flex-end;flex:none;margin-top:8px}\n.lks-input{flex:1;min-width:0;resize:none;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;color:var(--dsw-alias-label-primary);font-family:inherit;font-size:14px;line-height:1.6;padding:8px 10px}\n.lks-input:focus{outline:none;border-color:var(--dsw-alias-state-business-primary)}\n.lks-input:disabled{opacity:.55}\n.lks-import-hint{font-size:12.5px;color:var(--dsw-alias-label-tertiary);margin-top:6px;line-height:1.7}\n.lks-duebox{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:10px;padding:10px 12px;margin:8px 0 14px;font-size:13.5px}\n.lks-duebox .lks-due-item{color:var(--dsw-alias-label-secondary);margin-top:4px;display:flex;justify-content:space-between;gap:10px}\n.lks-duebox .lks-over{color:var(--dsw-alias-state-warn-label);flex:none}\n\n.lks-sec{font-size:12px;color:var(--dsw-alias-label-tertiary);text-transform:uppercase;letter-spacing:.05em;margin:14px 0 4px}\n.lks-node{display:flex;align-items:center;gap:8px;padding:6px 9px;border-radius:8px;margin:1px 0;cursor:pointer}\n.lks-node:hover{background:var(--dsw-alias-interactive-bg-hover)}\n.lks-node.focus{background:var(--dsw-alias-bg-layer-3);outline:1px solid var(--dsw-alias-border-l2)}\n.lks-node .lks-g{width:18px;text-align:center;flex:none}\n.lks-node .lks-t{flex:1;font-size:14px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.lks-node.locked{opacity:.45;cursor:not-allowed}\n.lks-node.locked:hover{background:none}\n.lks-tag{font-size:11px;border-radius:4px;padding:0 4px;flex:none}\n.lks-tag.weak{background:var(--dsw-alias-state-warn-tertiary);color:var(--dsw-alias-state-warn-label)}\n.lks-tag.fric{background:var(--dsw-alias-state-error-primary);color:#fff}\n.lks-bar{width:44px;height:4px;border-radius:2px;background:var(--dsw-alias-bg-layer-3);overflow:hidden;flex:none}\n.lks-bar i{display:block;height:100%;background:var(--dsw-alias-state-success-primary)}\n.lks-pct{font-size:12px;color:var(--dsw-alias-label-tertiary);flex:none;width:32px;text-align:right}\n\n.lks-empty{color:var(--dsw-alias-label-tertiary);text-align:center;padding:48px 8px;font-size:14px;line-height:2}\n.lks-actbar{display:flex;flex-direction:row;justify-content:flex-end;flex:none;padding-bottom:8px}\n\n/* ── middle column: the tutor ── */\n.lks-transcript{flex:1;min-height:0;overflow-y:auto;display:flex;flex-direction:column;gap:10px;padding:4px 2px}\n.lks-msg{max-width:94%;font-size:16px;line-height:1.7;border-radius:12px;padding:8px 12px;word-break:break-word}\n.lks-msg.user{align-self:flex-end;white-space:pre-wrap;background:var(--dsw-alias-state-business-tertiary);color:var(--dsw-alias-label-primary-bluish)}\n.lks-msg.assistant{align-self:flex-start;background:var(--dsw-alias-bg-layer-2)}\n.lks-msg.streaming{opacity:.7}\n.lks-msg.thinking{align-self:flex-start;display:inline-flex;align-items:center;gap:8px;font-size:13px;color:var(--dsw-alias-label-tertiary);background:none;border-radius:0;padding:2px}\n.lks-msg.tool{align-self:flex-start;font-size:12px;color:var(--dsw-alias-label-tertiary);background:none;border-radius:0;padding:1px 2px}\n.lks-msg.tool.ok{color:var(--dsw-alias-state-success-primary)}\n.lks-msg.tool.bad{color:var(--dsw-alias-state-error-primary)}\n.lks-msg.error{align-self:stretch;color:var(--dsw-alias-state-error-primary);font-size:13px;background:none;border-radius:0;padding:1px 2px}\n/* quiz-taking surface: the tutor's A–D options become clickable answers on the last settled reply */\n.lks-turn{display:flex;flex-direction:column;align-items:flex-start;max-width:94%}\n.lks-quiz{display:flex;flex-direction:column;gap:6px;width:100%;margin:2px 0 4px}\n.lks-opt{display:flex;align-items:baseline;gap:10px;text-align:left;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);border-radius:10px;padding:8px 14px;font-size:14px;line-height:1.5;color:var(--dsw-alias-label-secondary);cursor:pointer;font-family:inherit;transition:border-color .12s ease,color .12s ease,background .12s ease}\n.lks-opt:hover{border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-label-primary);background:var(--dsw-alias-state-business-tertiary)}\n.lks-opt:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-border-l3);outline:none}\n.lks-optletter{font-weight:700;color:var(--dsw-alias-label-primary-bluish);flex:none}\n\n/* soul pills (native tool-row trigger language: 28px transparent, tinted active) */\n.lks-pills{display:inline-flex;align-items:center;height:28px}\n.lks-pill{display:inline-flex;align-items:center;gap:6px;height:28px;padding:0 10px;border:none;border-radius:24px;background:transparent;color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:20px;font-weight:500;transition:background .12s ease,color .12s ease}\n.lks-pill:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}\n.lks-pill:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-border-l3);outline:none}\n.lks-pill.on{background:var(--dsw-alias-state-business-tertiary);color:var(--dsw-alias-label-primary-bluish)}\n.lks-pill.on:hover{background:var(--dsw-alias-state-business-tertiary);color:var(--dsw-alias-label-primary-bluish)}\n\n/* ic_ds_* glyph carriers + busy spinner (native .8s linear spin) */\n@keyframes lks-spin{to{transform:rotate(360deg)}}\n.lks-spin{animation:lks-spin .8s linear infinite}\n\n/* starter chips */\n.lks-dock{display:flex;align-items:center;flex-wrap:wrap;gap:6px;padding:0 2px 6px;flex:none}\n.lks-starter{border:1px solid var(--dsw-alias-border-l2);border-radius:999px;padding:4px 12px;font-size:13px;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2);transition:border-color .12s ease,color .12s ease}\n.lks-starter:hover{border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-label-primary)}\n\n/* proposal banner + shared buttons */\n.lks-banner{display:flex;align-items:center;gap:10px;background:var(--dsw-alias-state-warn-tertiary);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;padding:10px 14px;margin:8px 0;font-size:14px;flex:none}\n.lks-banner .lks-why{flex:1;color:var(--dsw-alias-label-secondary)}\n.lks-btn{display:inline-flex;align-items:center;gap:6px;border-radius:8px;padding:6px 14px;font-size:13.5px;font-weight:600;flex:none}\n.lks-btn.primary{background:var(--dsw-alias-state-business-primary);color:#fff}\n.lks-btn.primary:hover{filter:brightness(1.1)}\n.lks-btn.primary:disabled{opacity:.5;cursor:default;filter:none}\n.lks-btn.ghost{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);border:1px solid var(--dsw-alias-border-l2)}\n.lks-btn.ghost:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l3)}\n\n/* ── right column: blackboard ── */\n.lks-lessonhead h2{font-size:18px;margin:0 0 4px;font-weight:700}\n.lks-lessonhead .lks-meta{color:var(--dsw-alias-label-tertiary);font-size:13px;margin-bottom:4px}\n.lks-chips{display:flex;flex-wrap:wrap;gap:6px;margin:8px 0}\n.lks-chip{border-radius:999px;padding:2px 10px;font-size:12.5px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary)}\n.lks-chip.weak{background:var(--dsw-alias-state-warn-tertiary);color:var(--dsw-alias-state-warn-label)}\n\n/* rendered markdown (host-sanitized lesson bodies and assistant replies) */\n.lks-prose{font-size:16px;line-height:1.75}\n.lks-prose h1,.lks-prose h2,.lks-prose h3,.lks-prose h4{margin:14px 0 6px;line-height:1.4}\n.lks-prose p{margin:6px 0}\n.lks-prose pre{background:var(--dsw-alias-markdown-code-block);border:1px solid var(--dsw-alias-border-l1);border-radius:10px;padding:10px;overflow-x:auto;margin:8px 0;font-size:13.5px;font-family:var(--dsw-font-markdown-code-block-small)}\n.lks-prose code{font-family:var(--dsw-font-markdown-code);background:var(--dsw-alias-bg-layer-3);border-radius:4px;padding:1px 5px;font-size:.92em}\n.lks-prose pre code{background:none;padding:0}\n.lks-prose table{border-collapse:collapse;margin:8px 0}\n.lks-prose th,.lks-prose td{border:1px solid var(--dsw-alias-border-l2);padding:5px 11px;font-size:14px}\n.lks-prose th{background:var(--dsw-alias-bg-layer-2)}\n.lks-prose blockquote{border-left:3px solid var(--dsw-alias-state-business-primary);padding:2px 12px;color:var(--dsw-alias-label-secondary);margin:8px 0}\n.lks-prose ul,.lks-prose ol{padding-left:22px;margin:6px 0}\n.lks-prose img{max-width:100%}\n\n/* Cornell notes zones */\n.lks-bb-notes{margin-top:14px;padding-top:10px;border-top:1px solid var(--dsw-alias-border-l1)}\n.lks-zone{margin-bottom:14px}\n.lks-zone h4{font-size:14px;color:var(--dsw-alias-label-secondary);margin:0 0 8px;font-weight:600}\n.lks-note{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);border-radius:10px;padding:10px 14px;margin-bottom:8px}\n.lks-note .lks-note-src{float:right;font-size:10px;color:var(--dsw-alias-label-tertiary)}\n.lks-note .lks-note-title{font-weight:600;font-size:13px}\n.lks-note .lks-note-text{margin-top:4px;white-space:pre-wrap;font-size:13px;color:var(--dsw-alias-label-secondary)}\n.lks-note .lks-note-q{margin-top:6px;color:var(--dsw-alias-label-tertiary);font-size:12px;border-left:2px solid var(--dsw-alias-state-warn-primary);padding-left:8px}\n\n/* inline error text (write-action failures) */\n.lks-propcard-err{color:var(--dsw-alias-state-error-primary);font-size:12px;margin-top:6px;flex:none}\n`\n\n/**\n * Inject the study stylesheet into `document.head` (idempotent).\n * @returns the stylesheet element (existing one when already injected).\n */\nexport function ensureStudyStyles(): HTMLStyleElement {\n const existing = document.head.querySelector(`style[${STYLE_ID}]`)\n if (existing !== null) return existing\n const style = document.createElement('style')\n style.setAttribute(STYLE_ID, '')\n style.textContent = CSS\n document.head.appendChild(style)\n return style\n}\n","/**\n * ic_ds_* glyphs vendored from @deepseek-ai/dsh-client-ui-primitives\n * (src/icons/index.tsx, same figma source as the host's icon set) so the\n * plugin's client bundle stays self-contained per the dsh client-bundle\n * contract — only react and dsh-client-runtime/client are runtime-external;\n * importing the host package root would drag its CSS modules into the\n * inlined bundle. Re-sync these three paths if the host icon set redesigns\n * them; scripts/verify.mjs pins the path data.\n * @module dsh-plugin-lookatstudy/client/icons\n */\n\nimport { createElement } from 'react'\n\n/** Shared props for every vendored glyph (mirrors the host's IconProps). */\ninterface IconProps {\n /** Square edge in px; defaults to the glyph's own drawn size. */\n size?: number\n /** Extra class for layout placement; color rides currentColor. */\n className?: string\n}\n\n/** ic_ds_think_outline_16 */\nexport function IconThinkOutline16({ size = 16, className }: IconProps) {\n return createElement('svg', {\n width: size, height: size, className, viewBox: '0 0 16 16', fill: 'none', xmlns: 'http://www.w3.org/2000/svg',\n },\n createElement('path', {\n d: 'M8.00192 6.64454C8.75026 6.64454 9.35732 7.25169 9.35739 8.00001C9.35739 8.74838 8.7503 9.35548 8.00192 9.35548C7.25367 9.35533 6.64743 8.74829 6.64743 8.00001C6.6475 7.25178 7.25371 6.64468 8.00192 6.64454Z',\n fill: 'currentColor',\n }),\n createElement('path', {\n 'fill-rule': 'evenodd',\n 'clip-rule': 'evenodd',\n d: 'M9.97165 1.29981C11.5853 0.718916 13.271 0.642197 14.3144 1.68555C15.3577 2.72902 15.2811 4.41466 14.7002 6.02833C14.4707 6.66561 14.1504 7.32937 13.75 8.00001C14.1504 8.67062 14.4707 9.33444 14.7002 9.97169C15.2811 11.5854 15.3578 13.271 14.3144 14.3145C13.271 15.3579 11.5854 15.2811 9.97165 14.7002C9.3344 14.4708 8.67059 14.1505 7.99997 13.75C7.32933 14.1505 6.66558 14.4708 6.02829 14.7002C4.41461 15.2811 2.72899 15.3578 1.68552 14.3145C0.642155 13.271 0.71887 11.5854 1.29977 9.97169C1.52915 9.33454 1.84865 8.67049 2.24899 8.00001C1.84866 7.32953 1.52915 6.66544 1.29977 6.02833C0.718852 4.41459 0.64207 2.729 1.68552 1.68555C2.72897 0.642112 4.41456 0.718887 6.02829 1.29981C6.66541 1.52918 7.32949 1.8487 7.99997 2.24903C8.67045 1.84869 9.33451 1.52919 9.97165 1.29981ZM12.9404 9.2129C12.4391 9.893 11.8616 10.5681 11.2148 11.2149C10.568 11.8616 9.89296 12.4391 9.21286 12.9404C9.62532 13.1579 10.0271 13.338 10.4121 13.4766C11.9146 14.0174 12.9172 13.8738 13.3955 13.3955C13.8737 12.9173 14.0174 11.9146 13.4765 10.4121C13.3379 10.0271 13.1578 9.62535 12.9404 9.2129ZM3.05856 9.2129C2.84121 9.62523 2.66197 10.0272 2.52341 10.4121C1.98252 11.9146 2.12627 12.9172 2.60446 13.3955C3.08278 13.8737 4.08544 14.0174 5.58786 13.4766C5.97264 13.338 6.37389 13.1577 6.7861 12.9404C6.10624 12.4393 5.43168 11.8614 4.78513 11.2149C4.13823 10.5679 3.55992 9.89313 3.05856 9.2129ZM7.99899 3.792C7.23179 4.31419 6.45306 4.95512 5.70407 5.70411C4.95509 6.45309 4.31415 7.23184 3.79196 7.99903C4.3143 8.76666 4.95471 9.54653 5.70407 10.2959C6.45309 11.0449 7.23271 11.6848 7.99997 12.207C8.76725 11.6848 9.54683 11.0449 10.2959 10.2959C11.0449 9.54686 11.6848 8.76729 12.207 8.00001C11.6848 7.23275 11.0449 6.45312 10.2959 5.70411C9.5465 4.95475 8.76662 4.31434 7.99899 3.792ZM5.58786 2.52344C4.08533 1.98255 3.08272 2.12625 2.60446 2.6045C2.12621 3.08275 1.98252 4.08536 2.52341 5.5879C2.66189 5.97253 2.8414 6.37409 3.05856 6.78614C3.55983 6.10611 4.1384 5.43189 4.78513 4.78516C5.43186 4.13843 6.10606 3.55987 6.7861 3.0586C6.37405 2.84144 5.97249 2.66192 5.58786 2.52344ZM13.3955 2.6045C12.9172 2.12631 11.9146 1.98257 10.4121 2.52344C10.0272 2.66201 9.62519 2.84125 9.21286 3.0586C9.8931 3.55996 10.5679 4.13827 11.2148 4.78516C11.8614 5.43172 12.4392 6.10627 12.9404 6.78614C13.1577 6.37393 13.338 5.97267 13.4765 5.5879C14.0174 4.08549 13.8736 3.08281 13.3955 2.6045Z',\n fill: 'currentColor',\n }))\n}\n\n/** ic_ds_loading_outline_16 */\nexport function IconLoadingOutline16({ size = 16, className }: IconProps) {\n return createElement('svg', {\n width: size, height: size, className, viewBox: '0 0 16 16', fill: 'none', xmlns: 'http://www.w3.org/2000/svg',\n },\n createElement('path', {\n d: 'M2.871 13.1286C0.0387669 10.2962 0.0387669 5.70383 2.871 2.87141C5.70341 0.0390029 10.2957 0.0391154 13.1282 2.87141L12.1387 3.86094C9.85292 1.57538 6.1469 1.57596 3.86123 3.86163C1.57573 6.14732 1.57573 9.85269 3.86123 12.1384C6.1469 14.424 9.85292 14.4246 12.1387 12.1391L13.1282 13.1286C10.2957 15.9609 5.70341 15.961 2.871 13.1286Z',\n fill: 'currentColor',\n }))\n}\n\n/** ic_ds_download_outline_16 */\nexport function IconDownloadOutline16({ size = 16, className }: IconProps) {\n return createElement('svg', {\n width: size, height: size, className, viewBox: '0 0 16 16', fill: 'none', xmlns: 'http://www.w3.org/2000/svg',\n },\n createElement('path', {\n d: 'M15.3695 11.411L15.1234 12.8866C14.8869 14.3042 13.6603 15.3436 12.223 15.3436H3.77673C2.33958 15.3434 1.1128 14.3042 0.876343 12.8866L0.630249 11.411L2.05408 11.1747L2.29919 12.6493C2.41973 13.3713 3.04475 13.9001 3.77673 13.9003H12.223C12.9551 13.9002 13.58 13.3713 13.7006 12.6493L13.9457 11.1747L15.3695 11.411ZM8.72205 8.994C8.77717 8.93934 8.83792 8.88106 8.90271 8.81627L12.4828 5.23424L13.5043 6.25572L9.92224 9.8358C9.6395 10.1185 9.38763 10.3732 9.15857 10.5575C8.91892 10.7503 8.63953 10.9224 8.2865 10.9784C8.09711 11.0083 7.90363 11.0083 7.71423 10.9784C7.36106 10.9224 7.0809 10.7503 6.84119 10.5575C6.61215 10.3732 6.36022 10.1185 6.07751 9.8358L2.49646 6.25572L3.51697 5.23424L7.09705 8.81627C7.16219 8.88142 7.22331 8.94006 7.27869 8.99498V1.3065H8.72205V8.994Z',\n fill: 'currentColor',\n }))\n}\n","/**\n * Shared client-side study store: one module-level poller over the host\n * plugin's `/lookatstudy/api/state` endpoint, plus the three write actions\n * (focus, mode, reverse message). Every seat component binds through\n * {@link useStudy}; subscribers share the single 3 s poll cycle and the last\n * snapshot, so mounting more controls costs no extra requests.\n * @module dsh-plugin-lookatstudy/client/data\n */\n\nimport { useSyncExternalStore } from 'react'\n\n/** Wire shape of `GET /lookatstudy/api/state` (structural mirror of the host's WorkbenchState). */\nexport interface StudyState {\n readonly active: boolean\n readonly mode: 'direct' | 'guide' | 'practice'\n readonly courses: ReadonlyArray<{\n readonly courseId: string\n readonly title: string\n readonly mastered: number\n readonly total: number\n readonly avgMasteryPct: number | null\n readonly sections: ReadonlyArray<{\n readonly title: string\n readonly index: number\n readonly lessons: ReadonlyArray<{\n readonly id: string\n readonly title: string\n readonly kind: 'study' | 'practice' | 'exam'\n readonly status: string\n readonly masteryPct: number | null\n readonly weakConcepts: number\n readonly frictionCount: number\n readonly due: boolean\n readonly focus: boolean\n }>\n }>\n }>\n readonly focusLessonId: string | null\n readonly lesson: {\n readonly lessonId: string\n readonly courseTitle: string\n readonly sectionTitle: string\n readonly title: string\n readonly status: string\n readonly masteryPct: number | null\n readonly strategy: string\n readonly concepts: ReadonlyArray<{ title: string; masteryPct: number; weak: boolean }>\n readonly starters: ReadonlyArray<{ label: string; message: string }>\n readonly notes: ReadonlyArray<{ id: string; zone: string; title: string; text: string; source: string; quote: string | null }>\n readonly html: string\n } | null\n readonly dueCount: number\n readonly due: ReadonlyArray<{ lessonId: string; lessonTitle: string; courseTitle: string; overdueDays: number }>\n readonly pendingProposals: ReadonlyArray<{ id: string; lessonTitle: string; rationale: string }>\n readonly memory: { global: string | null; lesson: string | null; pattern: string | null }\n readonly lessonSessions: Readonly<Record<string, string>>\n}\n\n/** Poll cadence for the shared store; one cycle serves every mounted seat. */\nconst POLL_MS = 3_000\n\ntype Listener = () => void\n\nasync function fetchJson(url: string, init?: RequestInit): Promise<unknown> {\n const res = await fetch(url, init)\n const body: unknown = await res.json().catch(() => null)\n if (!res.ok) {\n const error = body !== null && typeof body === 'object' && 'error' in body && typeof body.error === 'string'\n ? body.error\n : `HTTP ${res.status}`\n throw new Error(error)\n }\n return body\n}\n\nclass StudyStore {\n private snapshot: StudyState | null = null\n private readonly listeners = new Set<Listener>()\n private timer: ReturnType<typeof setInterval> | undefined\n private inflight = false\n\n subscribe = (listener: Listener): (() => void) => {\n this.listeners.add(listener)\n if (this.listeners.size === 1) {\n void this.tick()\n this.timer = setInterval(() => { void this.tick() }, POLL_MS)\n }\n return () => {\n this.listeners.delete(listener)\n if (this.listeners.size === 0 && this.timer !== undefined) {\n clearInterval(this.timer)\n this.timer = undefined\n }\n }\n }\n\n getSnapshot = (): StudyState | null => this.snapshot\n\n private emit(): void {\n for (const listener of this.listeners) listener()\n }\n\n private async tick(): Promise<void> {\n if (this.inflight) return\n this.inflight = true\n try {\n const next = await fetchJson('/lookatstudy/api/state') as StudyState\n this.snapshot = next\n this.emit()\n } catch {\n // Host route absent (headless composition) or transient failure: keep\n // the last snapshot; seats render their loading/empty states.\n } finally {\n this.inflight = false\n }\n }\n\n /** One immediate re-read (after a write action). */\n refresh(): void {\n void this.tick()\n }\n\n /** Switch the tutor's soul mode directly (host persists and applies it on the next request). */\n async setMode(mode: StudyState['mode']): Promise<void> {\n await fetchJson('/lookatstudy/api/mode', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ mode }),\n })\n this.refresh()\n }\n\n /**\n * Flip learning mode. The host syncs its tool registry before responding,\n * so awaiting this guarantees the `study_*` tools exist for the next prompt.\n */\n async activate(active: boolean): Promise<void> {\n await fetchJson('/lookatstudy/api/active', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ active }),\n })\n this.refresh()\n }\n\n /** Point the tutor's focus at one lesson. */\n async setFocus(lessonId: string): Promise<void> {\n await fetchJson('/lookatstudy/api/focus', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ lessonId }),\n })\n this.refresh()\n }\n\n /** Record the dsh session backing one lesson (the simplified thread system). */\n async bindLessonSession(lessonId: string, sessionId: string): Promise<void> {\n await fetchJson('/lookatstudy/api/lesson-session', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ lessonId, sessionId }),\n })\n this.refresh()\n }\n\n /** Delete one course (and its proposals) from the host state. */\n async deleteCourse(courseId: string): Promise<void> {\n await fetchJson('/lookatstudy/api/course/delete', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ courseId }),\n })\n this.refresh()\n }\n}\n\n/** The one shared store instance backing every study seat component. */\nexport const studyStore = new StudyStore()\n\n/** React binding: live snapshot plus the write actions. */\nexport function useStudy(): {\n data: StudyState | null\n activate: (active: boolean) => Promise<void>\n setMode: (mode: StudyState['mode']) => Promise<void>\n setFocus: (lessonId: string) => Promise<void>\n deleteCourse: (courseId: string) => Promise<void>\n bindLessonSession: (lessonId: string, sessionId: string) => Promise<void>\n} {\n const data = useSyncExternalStore(studyStore.subscribe, studyStore.getSnapshot, studyStore.getSnapshot)\n return {\n data,\n activate: studyStore.activate.bind(studyStore),\n setMode: studyStore.setMode.bind(studyStore),\n setFocus: studyStore.setFocus.bind(studyStore),\n deleteCourse: studyStore.deleteCourse.bind(studyStore),\n bindLessonSession: studyStore.bindLessonSession.bind(studyStore),\n }\n}\n","/**\n * Server-side markdown → HTML for the study workbench's 讲解 view. Escapes\n * every HTML character first, then renders a pragmatic GFM subset (headings,\n * fenced code, inline code, bold/italic, links, lists, blockquotes, tables,\n * hr, paragraphs). Lesson bodies are imported teaching material, so raw HTML\n * never passes through.\n * @module dsh-plugin-lookatstudy/markdown\n */\n\n/** Escape all HTML-significant characters. */\nfunction escapeHtml(text: string): string {\n return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;')\n}\n\n/** Render inline markup (code, bold, italic, links) over escaped text. */\nfunction inline(escaped: string): string {\n return escaped\n .replace(/`([^`]+)`/g, '<code>$1</code>')\n .replace(/\\*\\*([^*]+)\\*\\*/g, '<strong>$1</strong>')\n .replace(/\\*([^*]+)\\*/g, '<em>$1</em>')\n .replace(/\\[([^\\]]+)\\]\\((https?:\\/\\/[^)\\s]+)\\)/g, '<a href=\"$2\" target=\"_blank\" rel=\"noreferrer\">$1</a>')\n}\n\n/** True when the line opens a GFM table row (pipes with a delimiter row next). */\nfunction isTableRow(line: string): boolean {\n return line.trim().startsWith('|') && line.trim().endsWith('|') && line.includes('|', 1)\n}\n\nfunction isDelimiterRow(line: string): boolean {\n const trimmed = line.trim()\n if (!trimmed.startsWith('|') || !trimmed.endsWith('|')) return false\n const cells = trimmed.replace(/^\\|/, '').replace(/\\|$/, '').split('|').map(c => c.trim())\n return cells.length > 0 && cells.every(c => /^:?-+:?$/.test(c))\n}\n\n/** Split a table row into trimmed cells (leading/trailing pipes removed). */\nfunction rowCells(line: string): string[] {\n return line.trim().replace(/^\\|/, '').replace(/\\|$/, '').split('|').map(c => c.trim())\n}\n\n/**\n * Render markdown text to sanitized HTML.\n * @param md - markdown source (lesson body).\n * @returns HTML string safe to inject into the page.\n */\nexport function renderMarkdown(md: string): string {\n const lines = escapeHtml(md).split(/\\r?\\n/)\n const out: string[] = []\n let i = 0\n\n const flushParagraph = (buffer: string[]): void => {\n if (buffer.length > 0) out.push(`<p>${inline(buffer.join(' '))}</p>`)\n }\n\n let paragraph: string[] = []\n while (i < lines.length) {\n const line = lines[i]!\n\n // Fenced code block\n const fence = /^```(\\w*)\\s*$/.exec(line.trim())\n if (fence) {\n flushParagraph(paragraph)\n paragraph = []\n const lang = fence[1] ?? ''\n const code: string[] = []\n i++\n while (i < lines.length && lines[i]!.trim() !== '```') {\n code.push(lines[i]!)\n i++\n }\n i++ // closing fence\n out.push(`<pre><code${lang === '' ? '' : ` class=\"lang-${lang}\"`}>${code.join('\\n')}</code></pre>`)\n continue\n }\n\n // Heading\n const heading = /^(#{1,6})\\s+(.*)$/.exec(line)\n if (heading) {\n flushParagraph(paragraph)\n paragraph = []\n const level = heading[1]!.length\n out.push(`<h${level}>${inline(heading[2]!)}</h${level}>`)\n i++\n continue\n }\n\n // Horizontal rule\n if (/^\\s*(-{3,}|\\*{3,}|_{3,})\\s*$/.test(line)) {\n flushParagraph(paragraph)\n paragraph = []\n out.push('<hr>')\n i++\n continue\n }\n\n // Blockquote\n if (/^\\s*&gt;\\s?/.test(line)) {\n flushParagraph(paragraph)\n paragraph = []\n const quote: string[] = []\n while (i < lines.length && /^\\s*&gt;\\s?/.test(lines[i]!)) {\n quote.push(lines[i]!.replace(/^\\s*&gt;\\s?/, ''))\n i++\n }\n out.push(`<blockquote><p>${inline(quote.join(' '))}</p></blockquote>`)\n continue\n }\n\n // Unordered list\n if (/^\\s*[-*+]\\s+/.test(line)) {\n flushParagraph(paragraph)\n paragraph = []\n const items: string[] = []\n while (i < lines.length && /^\\s*[-*+]\\s+/.test(lines[i]!)) {\n items.push(`<li>${inline(lines[i]!.replace(/^\\s*[-*+]\\s+/, ''))}</li>`)\n i++\n }\n out.push(`<ul>${items.join('')}</ul>`)\n continue\n }\n\n // Ordered list\n if (/^\\s*\\d+\\.\\s+/.test(line)) {\n flushParagraph(paragraph)\n paragraph = []\n const items: string[] = []\n while (i < lines.length && /^\\s*\\d+\\.\\s+/.test(lines[i]!)) {\n items.push(`<li>${inline(lines[i]!.replace(/^\\s*\\d+\\.\\s+/, ''))}</li>`)\n i++\n }\n out.push(`<ol>${items.join('')}</ol>`)\n continue\n }\n\n // GFM table\n if (isTableRow(line) && i + 1 < lines.length && isDelimiterRow(lines[i + 1]!)) {\n flushParagraph(paragraph)\n paragraph = []\n const headers = rowCells(line)\n i += 2\n const rows: string[] = []\n while (i < lines.length && isTableRow(lines[i]!)) {\n const cells = rowCells(lines[i]!)\n rows.push(`<tr>${cells.map(c => `<td>${inline(c)}</td>`).join('')}</tr>`)\n i++\n }\n out.push(`<table><thead><tr>${headers.map(h => `<th>${inline(h)}</th>`).join('')}</tr></thead><tbody>${rows.join('')}</tbody></table>`)\n continue\n }\n\n // Blank line ends the paragraph\n if (line.trim() === '') {\n flushParagraph(paragraph)\n paragraph = []\n i++\n continue\n }\n\n paragraph.push(line.trim())\n i++\n }\n flushParagraph(paragraph)\n return out.join('\\n')\n}\n","/**\n * The study tab: one `conversation.view` entry rendering the whole plugin —\n * a simplified LookatStudy inside dsh, three columns in the tab. Left: the\n * course map (selector, tree, due box, one-click demo import). Middle: the\n * tutor conversation — a read-only mini transcript folded from the session\n * snapshot (user text, assistant text rendered through the plugin's own\n * markdown pipeline, tool chips) plus the soul pills, starters, pending\n * proposal banner, and a reverse-channel input. Right: the blackboard —\n * focus-lesson 讲解 and the Cornell 笔记 zones. Nothing outside this tab\n * touches dsh chrome.\n * @module dsh-plugin-lookatstudy/client/views\n */\n\nimport { createElement, useEffect, useRef, useState } from 'react'\nimport type { KeyboardEvent as ReactKeyboardEvent, ReactNode } from 'react'\nimport { IconDownloadOutline16, IconLoadingOutline16 } from './icons.tsx'\nimport type { ConvViewProps } from '@deepseek-ai/dsh-client-ui-conversation/client'\nimport type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'\nimport type {\n AssistantBlock, ConversationNode, ConversationSnapshot, PartialAssistant,\n} from '@deepseek-ai/dsh-client-runtime/client'\nimport { useStudy } from './data.ts'\nimport { renderMarkdown } from '../markdown.ts'\n\n/** The three souls, in pill order (labels from LookatStudy's mode switcher). */\nconst MODES: ReadonlyArray<{ id: 'direct' | 'guide' | 'practice'; label: string; hint: string }> = [\n { id: 'direct', label: '直讲', hint: 'direct 精讲:先讲清楚,再确认懂没懂' },\n { id: 'guide', label: '引导', hint: 'guide 引导:让你自己往前推一步,导师递台阶' },\n { id: 'practice', label: '实战', hint: 'practice 实战:在真实世界的乱问题里学' },\n]\n\n/** Zone labels for the Cornell notebook (understand / record / practice). */\nconst ZONES: ReadonlyArray<readonly [string, string]> = [\n ['understand', '🧠 理解区 — 知识结构'],\n ['record', '📝 记录区 — 我的话'],\n ['practice', '✍️ 练习区 — 答题日志'],\n]\n\n/** Status glyph for one lesson row (LookatStudy's map icons). */\nfunction glyph(kind: string, status: string): string {\n if (kind === 'exam') return '🎯'\n if (status === 'mastered') return '👑'\n if (status === 'in_progress') return '📖'\n if (status === 'available') return '⭐'\n return '🔒'\n}\n\n/** LookatStudy's exam gate: an exam node opens only when every sibling study lesson reached mastery ≥50%. */\nfunction examOpen(lessons: ReadonlyArray<{ kind: string; masteryPct: number | null }>): boolean {\n return lessons.every(l => l.kind !== 'study' || (l.masteryPct ?? 0) >= 50)\n}\n\n/** Multi-keyword AND title filter (LookatStudy course-tree-filter). */\nfunction titleMatches(title: string, query: string): boolean {\n const keys = query.trim().toLowerCase().split(/\\s+/).filter(Boolean)\n return keys.every(k => title.toLowerCase().includes(k))\n}\n\n/** The rail's import row: a GitHub URL input plus the paste/folder hints. */\nfunction ImportRow({ send }: { send: StudySend }): ReactNode {\n const [url, setUrl] = useState('')\n return createElement('div', { className: 'lks-import' },\n createElement('div', { className: 'lks-inputrow' },\n createElement('input', {\n className: 'lks-input',\n type: 'url',\n placeholder: 'GitHub 仓库链接,如 microsoft/AI-For-Beginners',\n value: url,\n onChange: (e: { target: { value: string } }) => { setUrl(e.target.value) },\n onKeyDown: (e: ReactKeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'Enter' && url.trim() !== '') send(`导入课程:用 study_import_github 抓取 ${url.trim()}`)\n },\n }),\n createElement('button', {\n className: 'lks-btn primary',\n disabled: url.trim() === '',\n onClick: () => { send(`导入课程:用 study_import_github 抓取 ${url.trim()}`) },\n }, '导入'),\n ),\n createElement('div', { className: 'lks-import-hint' },\n '粘贴 markdown → 说「导入为课程」', createElement('br'),\n '本地文件夹 → 说「导入 D:/path/to/folder」'),\n )\n}\n\n/** One rendered transcript row (pure fold of the conversation snapshot). */\nexport interface ChatRow {\n readonly key: string\n readonly role: 'user' | 'assistant' | 'tool' | 'error' | 'streaming' | 'thinking'\n readonly text: string\n /** The settled tool call head for `tool` rows; drives the learning-aware chip. */\n readonly call?: { name: string; argsRaw: string } | null\n}\n\n/**\n * Tooltip text for one course-tree status glyph. Pure.\n * @param kind - lesson kind ('study' | 'practice' | 'exam').\n * @param status - lesson status ('locked' | 'available' | 'in_progress' | 'mastered').\n */\nexport function statusTitle(kind: string, status: string): string {\n if (kind === 'exam') return '章节测验:本节全部课时掌握度 ≥50% 后开放'\n switch (status) {\n case 'mastered': return '已毕业:掌握度 ≥90%(或你接受了掌握提案)'\n case 'in_progress': return '学习中:已开课,掌握度从 50% 起步'\n case 'available': return '可开始:已解锁,尚未学习'\n default: return '未解锁:先完成前面的课时'\n }\n}\n\n/**\n * Extract the last quiz option block (2–4 consecutive `A. …` `B. …` lines,\n * letters strictly consecutive from A) from an assistant reply — the raw text\n * of the tutor's question becomes the clickable answer surface. Pure.\n * @param text - one assistant reply's markdown.\n * @returns the options of the last valid block, or [] when none.\n */\nexport function quizOptions(text: string): ReadonlyArray<{ letter: string; text: string }> {\n const runs: Array<Array<{ letter: string; text: string }>> = []\n let run: Array<{ letter: string; text: string }> = []\n const flush = (): void => {\n if (run.length >= 2) runs.push(run)\n run = []\n }\n for (const line of text.split('\\n')) {\n const match = /^([A-D])[.、:)]\\s+(.+)$/.exec(line.trim())\n if (match === null) {\n flush()\n continue\n }\n const nextLetter = run.length === 0 ? 'A' : String.fromCharCode(run[run.length - 1]!.letter.charCodeAt(0) + 1)\n if (match[1] === nextLetter) run.push({ letter: match[1], text: match[2]! })\n else {\n flush()\n if (match[1] === 'A') run.push({ letter: match[1], text: match[2]! })\n }\n }\n flush()\n return runs.at(-1) ?? []\n}\n\n/**\n * Learning-aware label for one settled tool call; null keeps the generic\n * 🔧-name chip. Pure.\n * @param name - tool call name.\n * @param argsRaw - the call's raw JSON args.\n */\nexport function toolChipLabel(name: string, argsRaw: string): { label: string; tone: 'ok' | 'bad' } | null {\n if (name === 'study_record_answer') {\n let concept = '未归因'\n let correct = false\n try {\n const args = JSON.parse(argsRaw) as { concept?: unknown; correct?: unknown }\n if (typeof args.concept === 'string' && args.concept !== '') concept = args.concept\n correct = args.correct === true\n } catch { /* malformed argsRaw falls back to the unattributed/incorrect label */ }\n return correct\n ? { label: `✓ 答对 · ${concept}`, tone: 'ok' }\n : { label: `✗ 答错 · ${concept}`, tone: 'bad' }\n }\n if (name === 'study_import_github' || name === 'study_import_markdown' || name === 'study_import_folder') return { label: '📦 导入课程', tone: 'ok' }\n if (name === 'study_define_concepts') return { label: '🧠 提炼知识点', tone: 'ok' }\n return null\n}\n\nfunction textOf(blocks: readonly { type: string; text?: string }[]): string {\n return blocks.filter(b => b.type === 'text').map(b => b.text ?? '').join('\\n\\n').trim()\n}\n\nfunction assistantText(blocks: readonly AssistantBlock[]): string {\n return blocks.filter(b => b.kind === 'text').map(b => b.text).join('\\n\\n').trim()\n}\n\n/**\n * Fold conversation nodes (plus the streaming partial) into render rows;\n * tool activity condenses to one muted chip per settled call.\n * @param nodes - finalized conversation nodes from the snapshot.\n * @param partial - the in-flight assistant partial, or null.\n * @returns ordered rows; never mutates its inputs.\n */\nexport function transcriptRows(nodes: readonly ConversationNode[], partial: PartialAssistant | null): readonly ChatRow[] {\n const rows: ChatRow[] = []\n for (const node of nodes) {\n switch (node.kind) {\n case 'user':\n case 'steering': {\n const text = textOf(node.content)\n if (text !== '') rows.push({ key: `u${node.seq}`, role: 'user', text })\n break\n }\n case 'assistant': {\n const text = assistantText(node.blocks)\n if (text !== '') rows.push({ key: `a${node.seq}`, role: 'assistant', text })\n break\n }\n case 'tool-result':\n rows.push({ key: `t${node.seq}`, role: 'tool', text: node.call?.name ?? node.callId, call: node.call })\n break\n case 'turn-error':\n rows.push({ key: `e${node.seq}`, role: 'error', text: node.message })\n break\n default:\n break\n }\n }\n if (partial !== null) {\n const text = assistantText(partial.blocks)\n if (text !== '') rows.push({ key: 'streaming', role: 'streaming', text })\n // No text yet means the tutor is reasoning (or lining up tool calls) —\n // without this row the column sits dead silent through the whole phase.\n else rows.push({ key: 'thinking', role: 'thinking', text: '导师思考中…' })\n }\n return rows\n}\n\n/** Small inline error surface for failed write actions. */\nfunction ActionError({ error }: { error: string | null }): ReactNode {\n if (error === null) return null\n return createElement('div', { className: 'lks-propcard-err' }, error)\n}\n\n/**\n * Land one message in the dsh composer and submit it — the same path as the\n * Send button, so button text is visible in the native input for a beat and\n * then goes through the full submission pipeline (no reverse channel).\n */\nexport type StudySend = (text: string) => void\n\n/** Narrow-mode pane selector: which of the three columns is the main display. */\ntype StudyPane = 'rail' | 'tutor' | 'bb'\n\n/** sessionStorage key keeping the narrow-mode pane across view remounts (tab switches) and reloads. */\nconst PANE_KEY = 'dsh-plugin-lookatstudy:pane'\n\n/** Narrow the stored value back to a pane id; anything else (or nothing stored) falls back to 导师. Pure, unit-testable. */\nexport function pickPane(stored: string | null): StudyPane {\n return stored === 'rail' || stored === 'tutor' || stored === 'bb' ? stored : 'tutor'\n}\n\n/** Read the persisted pane; the try swallows only the SecurityError privacy modes throw on storage access — the fallback is the default pane. */\nfunction storedPane(): StudyPane {\n try {\n return pickPane(sessionStorage.getItem(PANE_KEY))\n } catch {\n return 'tutor'\n }\n}\n\n/** The whole study tab. Wide = three columns (课程 | 导师 | 黑板); narrow (<1220px) = one composer-width pane with a three-way switcher. */\nexport function studyView(ctx: ClientContext): (props: ConvViewProps) => ReactNode {\n return function StudyView(props: ConvViewProps): ReactNode {\n return createElement(StudyTab, { ...props, ctx, key: 'tab' })\n }\n}\n\n/** Tab body: the factory-bound ctx carries workspaces/sessions for the per-lesson session jumps. */\nfunction StudyTab({ useSession, inputActions, ctx }: ConvViewProps & { ctx: ClientContext }): ReactNode {\n const { data, activate, setMode, setFocus, deleteCourse, bindLessonSession } = useStudy()\n const snapshot = useSession((s: ConversationSnapshot) => s)\n const [pane, setPane] = useState<StudyPane>(storedPane)\n const send: StudySend = (text) => {\n void (async () => {\n // Dormant installs activate first: the host registers the study tools\n // before the activation POST resolves, so the prompt meets a model that\n // can already call them.\n if (data?.active !== true) await activate(true)\n inputActions.setDraft(text)\n inputActions.submit()\n })()\n }\n const panes: ReadonlyArray<{ id: StudyPane; label: string }> = [\n { id: 'rail', label: '课程' },\n { id: 'tutor', label: '导师' },\n { id: 'bb', label: '黑板' },\n ]\n return createElement('div', {\n className: 'lks-root lks-study',\n 'data-conversation-composer-overlay': '',\n 'data-pane': pane,\n },\n // Always-visible activation bar: the tab is the fallback entry when another\n // plugin's composer takeover hides the starter pill.\n createElement('div', { className: 'lks-actbar' },\n data === null ? null : createElement('button', {\n className: `lks-btn ${data.active ? 'ghost' : 'primary'}`,\n title: data.active\n ? '注销 study 工具与导师人格;学习进度保留,可随时重新开启'\n : '注册 study 工具并载入导师人格,之后普通对话和现在一样',\n onClick: () => { void activate(!data.active) },\n }, data.active ? '⏻ 退出学习模式' : '▶ 开始学习'),\n ),\n // .lks-body carries the row/column direction so the container query can\n // flip it — a container query cannot style the container element itself.\n createElement('div', { className: 'lks-body' },\n createElement('div', { className: 'lks-switch' },\n ...panes.map(p => createElement('button', {\n key: p.id,\n className: `lks-switch-btn${pane === p.id ? ' on' : ''}`,\n onClick: () => {\n setPane(p.id)\n try {\n sessionStorage.setItem(PANE_KEY, p.id)\n } catch {\n // Privacy modes forbid storage writes; the in-memory choice still holds this page.\n }\n },\n }, p.label)),\n ),\n createElement(CourseRail, { data, activate, setFocus, deleteCourse, bindLessonSession, send, ctx, currentSessionId: snapshot.sessionId }),\n createElement(TutorColumn, { data, setMode, send, snapshot }),\n createElement(BlackboardColumn, { data }),\n ),\n )\n}\n\ntype StudyData = ReturnType<typeof useStudy>['data']\n\n/** Left column: course management (pick/delete/search/import), lesson tree, due box. */\nfunction CourseRail({ data, activate, setFocus, deleteCourse, bindLessonSession, send, ctx, currentSessionId }: {\n data: StudyData\n activate: (active: boolean) => Promise<void>\n setFocus: (id: string) => Promise<void>\n deleteCourse: (courseId: string) => Promise<void>\n bindLessonSession: (lessonId: string, sessionId: string) => Promise<void>\n send: StudySend\n ctx: ClientContext\n currentSessionId: string\n}): ReactNode {\n const [jumping, setJumping] = useState<string | null>(null)\n /** Open (or mint) the lesson's own session — the simplified thread system. */\n const openLessonThread = (lesson: { id: string; title: string }): void => {\n if (jumping !== null) return\n const mapped = data?.lessonSessions[lesson.id]\n if (mapped === currentSessionId) {\n void setFocus(lesson.id).catch(reportError)\n return\n }\n if (mapped !== undefined && ctx.sessions.binding(mapped as never) !== undefined) {\n void setFocus(lesson.id).catch(reportError)\n ctx.sessions.open(mapped as never)\n return\n }\n setJumping(lesson.id)\n void setFocus(lesson.id).then(() => (async () => {\n // Minting a lesson session prompts directly (not through `send`), so\n // activation happens here too — same dormant-install guarantee.\n if (data?.active !== true) await activate(true)\n const area = await fetch('/lookatstudy/api/study-workspace')\n if (!area.ok) throw new Error(`study area unavailable (HTTP ${area.status})`)\n const { path } = await area.json() as { path: string }\n const workspace = await ctx.workspaces.create({ path })\n // connectWorkspace mints a fresh blank session once the workspace's\n // previous blank is used — each lesson thereby gets its own thread.\n const sessionId = await ctx.workspaces.connectWorkspace(workspace.workspaceId)\n const actx = ctx.sessions.scope(sessionId)\n const face = actx === undefined ? undefined : ctx.sessions.sessionOf(actx)\n if (face === undefined) throw new Error('lesson session is not addressable yet')\n const result = await face.prompt([{ type: 'text', text: `学习「${lesson.title}」:用 study_lesson 打开这一课开始学习。` }], 'queue')\n if (!result.ok) throw new Error(`lesson prompt rejected: ${result.error.code}: ${result.error.message}`)\n await bindLessonSession(lesson.id, sessionId)\n ctx.sessions.open(sessionId)\n })()).then(\n () => { setJumping(null) },\n (err: unknown) => { reportError(err); setJumping(null) },\n )\n }\n const [selectedCourse, setSelectedCourse] = useState('')\n const [query, setQuery] = useState('')\n const [confirmDelete, setConfirmDelete] = useState(false)\n const [showImport, setShowImport] = useState(false)\n const [error, setError] = useState<string | null>(null)\n const reportError = (err: unknown): void => { setError(err instanceof Error ? err.message : String(err)) }\n const body: ReactNode = data === null\n ? createElement('div', { className: 'lks-empty' }, '加载中…')\n : data.courses.length === 0\n ? createElement('div', { className: 'lks-empty' },\n '暂无课程', createElement('br'),\n createElement('button', {\n className: 'lks-btn primary',\n style: { margin: '10px 0' },\n onClick: () => { send('导入课程:用 study_import_github 抓取 https://github.com/microsoft/AI-For-Beginners') },\n }, createElement(IconDownloadOutline16, null), '导入示例课程'),\n createElement(ImportRow, { send }),\n )\n : (() => {\n const courseId = data.courses.some(c => c.courseId === selectedCourse)\n ? selectedCourse\n : data.courses[0]!.courseId\n const course = data.courses.find(c => c.courseId === courseId)!\n return createElement('div', null,\n createElement('div', { className: 'lks-rail-head' },\n data.courses.length > 1\n ? createElement('select', {\n className: 'lks-rail-select',\n value: courseId,\n onChange: (e: { target: { value: string } }) => { setSelectedCourse(e.target.value); setConfirmDelete(false) },\n }, ...data.courses.map(c => createElement('option', { key: c.courseId, value: c.courseId }, c.title)))\n : createElement('div', { className: 'lks-rail-title' }, course.title),\n createElement('button', {\n className: `lks-btn ${confirmDelete ? 'primary' : 'ghost'}`,\n title: confirmDelete ? '再点一次确认删除(含全部进度与笔记)' : '删除本课程',\n onClick: () => {\n if (!confirmDelete) { setConfirmDelete(true); return }\n setConfirmDelete(false)\n deleteCourse(courseId).then(() => { setSelectedCourse('') }, reportError)\n },\n }, confirmDelete ? '确认删除?' : '🗑'),\n ),\n createElement('div', { className: 'lks-rail-sub' }, `${course.mastered}/${course.total} 已掌握`),\n createElement('div', {\n className: `lks-masterybar${course.avgMasteryPct === 100 ? ' gold' : ''}`,\n title: course.avgMasteryPct === null ? '尚无掌握度数据' : `平均掌握度 ${course.avgMasteryPct}%`,\n }, createElement('i', { style: { width: `${course.avgMasteryPct ?? 0}%` } })),\n createElement('input', {\n className: 'lks-search',\n type: 'search',\n placeholder: '搜索课时…(多关键词空格分隔)',\n value: query,\n onChange: (e: { target: { value: string } }) => { setQuery(e.target.value) },\n onKeyDown: (e: ReactKeyboardEvent<HTMLInputElement>) => {\n if (e.key !== 'Enter' || query.trim() === '') return\n for (const section of course.sections) {\n const hit = section.lessons.find(l => l.kind !== 'exam' && l.status !== 'locked' && titleMatches(l.title, query))\n if (hit !== undefined) {\n void setFocus(hit.id).catch(reportError)\n return\n }\n }\n },\n }),\n data.dueCount > 0\n ? createElement('div', { className: 'lks-duebox' },\n `🔁 待复习 ${data.dueCount}`,\n ...data.due.map(d => createElement('div', { key: d.lessonId, className: 'lks-due-item' },\n createElement('span', null, d.lessonTitle),\n d.overdueDays > 0 ? createElement('span', { className: 'lks-over' }, `超${d.overdueDays}天`) : null)),\n createElement('button', {\n className: 'lks-btn ghost',\n style: { marginTop: '6px' },\n onClick: () => { send('开始今天的复习,从最到期的课时开始') },\n }, '开始复习'),\n )\n : null,\n ...course.sections.flatMap(section => {\n const examAllowed = examOpen(section.lessons)\n const lessons = section.lessons.filter(l => query.trim() === '' || titleMatches(l.title, query) || l.focus)\n if (lessons.length === 0) return []\n return [\n createElement('div', { key: section.title, className: 'lks-sec' },\n createElement('span', { className: 'lks-sec-num' }, String(section.index + 1)),\n ` ${section.title}`),\n ...lessons.map(lesson => {\n const locked = lesson.status === 'locked' || (lesson.kind === 'exam' && !examAllowed)\n return createElement('div', {\n key: lesson.id,\n className: `lks-node${locked ? ' locked' : ''}${lesson.focus ? ' focus' : ''}`,\n title: jumping === lesson.id ? '正在打开课时会话…' : statusTitle(lesson.kind, locked ? 'locked' : lesson.status),\n onClick: () => {\n if (locked) return\n if (lesson.kind === 'exam') {\n void setFocus(lesson.id).catch(reportError)\n send(`开始「${section.title}」的章节测验:按本节课时出题,答完逐题判分`)\n return\n }\n openLessonThread(lesson)\n },\n },\n createElement('span', { className: 'lks-g' }, jumping === lesson.id ? '⏳' : glyph(lesson.kind, locked ? 'locked' : lesson.status)),\n createElement('span', { className: 'lks-t' }, lesson.title),\n lesson.due ? createElement('span', { className: 'lks-tag due', title: '这课时的复习今天到期(SM-2)' }, '🔁') : null,\n lesson.weakConcepts > 0 ? createElement('span', { className: 'lks-tag weak', title: `${lesson.weakConcepts} 个薄弱知识点,测验会优先考察` }, `⚡${lesson.weakConcepts}`) : null,\n lesson.frictionCount > 0 ? createElement('span', { className: 'lks-tag fric', title: `${lesson.frictionCount} 次卡点记录(你说\"不懂\"时导师记下的)` }, `😣${lesson.frictionCount}`) : null,\n lesson.masteryPct !== null\n ? createElement('span', { className: 'lks-bar', title: `课时掌握度 ${lesson.masteryPct}%(取最薄弱知识点)` }, createElement('i', { style: { width: `${lesson.masteryPct}%` } }))\n : null,\n lesson.masteryPct !== null ? createElement('span', { className: 'lks-pct', title: '课时掌握度 = 最薄弱知识点的掌握度' }, `${lesson.masteryPct}%`) : null,\n )\n }),\n ]\n }),\n createElement('button', {\n className: 'lks-btn ghost',\n style: { marginTop: '10px' },\n onClick: () => { setShowImport(!showImport) },\n }, showImport ? '收起导入' : '+ 导入课程'),\n showImport ? createElement(ImportRow, { send }) : null,\n )\n })()\n return createElement('div', { className: 'lks-col lks-col-rail' },\n createElement('div', { className: 'lks-colhead' }, '课程'),\n body,\n createElement(ActionError, { error }),\n )\n}\n\n/** One transcript row's element; `interactive` adds the quiz-answer buttons to an assistant row. */\nfunction chatRowElement(row: ChatRow, interactive?: { send: StudySend }): ReactNode {\n if (row.role === 'user') {\n return createElement('div', { key: row.key, className: 'lks-msg user' }, row.text)\n }\n if (row.role === 'tool') {\n const chip = row.call === undefined || row.call === null ? null : toolChipLabel(row.call.name, row.call.argsRaw)\n if (chip !== null) {\n return createElement('div', { key: row.key, className: `lks-msg tool ${chip.tone}` }, chip.label)\n }\n return createElement('div', { key: row.key, className: 'lks-msg tool' }, `🔧 ${row.text}`)\n }\n if (row.role === 'error') {\n return createElement('div', { key: row.key, className: 'lks-msg error' }, `⚠ ${row.text}`)\n }\n if (row.role === 'thinking') {\n return createElement('div', { key: row.key, className: 'lks-msg thinking', title: '导师正在推理,回复马上就来' },\n createElement(IconLoadingOutline16, { size: 14, className: 'lks-spin' }),\n row.text)\n }\n const cls = row.role === 'streaming' ? 'lks-msg assistant lks-prose streaming' : 'lks-msg assistant lks-prose'\n const body = createElement('div', {\n key: row.key,\n className: cls,\n dangerouslySetInnerHTML: { __html: renderMarkdown(row.text) },\n })\n const options = interactive === undefined || row.role === 'streaming' ? [] : quizOptions(row.text)\n if (options.length < 2) return body\n return createElement('div', { key: row.key, className: 'lks-turn' },\n body,\n createElement('div', { className: 'lks-quiz', title: '点击选项作答,也可以直接打字回答' },\n ...options.map(opt => createElement('button', {\n key: opt.letter,\n className: 'lks-opt',\n onClick: () => { interactive.send(`选 ${opt.letter}:${opt.text}`) },\n },\n createElement('span', { className: 'lks-optletter' }, opt.letter),\n createElement('span', null, opt.text))),\n ))\n}\n\n/** Middle column: the tutor — transcript, proposal banner, pills, starters. Typing happens in the native composer below the tab. */\nfunction TutorColumn({ data, setMode, send, snapshot }: {\n data: StudyData\n setMode: (mode: 'direct' | 'guide' | 'practice') => Promise<void>\n send: StudySend\n snapshot: ConversationSnapshot\n}): ReactNode {\n const [error, setError] = useState<string | null>(null)\n const rows = transcriptRows(snapshot.nodes, snapshot.partial)\n // Quiz buttons only on the last settled assistant reply: older questions are\n // already answered, and the streaming partial may cut an option mid-line.\n const lastAssistant = rows.reduce((acc, row, i) => row.role === 'assistant' ? i : acc, -1)\n const scrollRef = useRef<HTMLDivElement | null>(null)\n useEffect(() => {\n const el = scrollRef.current\n if (el !== null) el.scrollTop = el.scrollHeight\n }, [rows.length])\n const proposal = data?.pendingProposals[0] ?? null\n const lesson = data?.lesson ?? null\n /** Mode switches are the only fallible write left here (host route); surface failures inline. */\n const fire = (action: Promise<void>): void => {\n action.then(() => { setError(null) }, (err: unknown) => { setError(err instanceof Error ? err.message : String(err)) })\n }\n return createElement('div', { className: 'lks-col lks-col-tutor' },\n createElement('div', { className: 'lks-colhead' }, '导师'),\n createElement('div', { className: 'lks-transcript', ref: scrollRef },\n rows.length === 0\n ? createElement('div', { className: 'lks-empty' }, '对话会出现在这里', createElement('br'), '在下方输入框和导师说话')\n : rows.map((row, i) => chatRowElement(row, i === lastAssistant ? { send } : undefined)),\n ),\n proposal !== null\n ? createElement('div', { className: 'lks-banner' },\n createElement('span', null, '🎓'),\n createElement('span', { className: 'lks-why' }, `导师提议你已掌握「${proposal.lessonTitle}」:${proposal.rationale}`),\n createElement('button', {\n className: 'lks-btn primary',\n onClick: () => { send(`接受提案 ${proposal.id} —— 确认标记这课为已掌握`) },\n }, '接受'),\n createElement('button', {\n className: 'lks-btn ghost',\n onClick: () => { send(`拒绝提案 ${proposal.id} —— 我想再练练`) },\n }, '再练练'),\n )\n : null,\n createElement('div', { className: 'lks-pills', style: { height: 'auto', padding: '4px 0' } },\n ...MODES.map(mode => createElement('button', {\n key: mode.id,\n className: `lks-pill${data?.mode === mode.id ? ' on' : ''}`,\n title: mode.hint,\n onClick: () => { fire(setMode(mode.id)) },\n }, mode.label)),\n ),\n lesson !== null && lesson.starters.length > 0\n ? createElement('div', { className: 'lks-dock', style: { padding: '0 0 6px' } },\n ...lesson.starters.map(s => createElement('button', {\n key: s.label,\n className: 'lks-starter',\n title: s.message,\n onClick: () => { send(s.message) },\n }, s.label)),\n )\n : null,\n createElement(ActionError, { error }),\n )\n}\n\n/** Right column: the blackboard — focus-lesson 讲解 plus the Cornell 笔记. */\nfunction BlackboardColumn({ data }: { data: StudyData }): ReactNode {\n const lesson = data?.lesson ?? null\n const body: ReactNode = lesson === null\n ? createElement('div', { className: 'lks-empty' }, '黑板还空着', createElement('br'), '在左侧课程树选择一课')\n : createElement('div', null,\n createElement('div', { className: 'lks-lessonhead' },\n createElement('h2', null, lesson.title),\n createElement('div', { className: 'lks-meta' },\n `${lesson.courseTitle} · ${lesson.status}`\n + (lesson.masteryPct === null ? '' : ` · 掌握度 ${lesson.masteryPct}%`)\n + ` · ${lesson.strategy}`),\n lesson.concepts.length > 0\n ? createElement('div', { className: 'lks-chips' },\n ...lesson.concepts.map(c => createElement('span', {\n key: c.title,\n className: `lks-chip${c.weak ? ' weak' : ''}`,\n }, `${c.title} ${c.masteryPct}%${c.weak ? ' ⚡' : ''}`)))\n : null,\n ),\n createElement('div', { className: 'lks-prose', dangerouslySetInnerHTML: { __html: lesson.html } }),\n createElement('div', { className: 'lks-bb-notes' },\n createElement('div', { className: 'lks-sec' }, '笔记'),\n lesson.notes.length === 0\n ? createElement('div', { className: 'lks-empty', style: { padding: '16px 0' } }, '这一课还没有笔记')\n : ZONES.filter(([zone]) => lesson.notes.some(n => n.zone === zone)).map(([zone, label]) =>\n createElement('div', { key: zone, className: 'lks-zone' },\n createElement('h4', null, label),\n ...lesson.notes.filter(n => n.zone === zone).map(n => createElement('div', { key: n.id, className: 'lks-note' },\n createElement('span', { className: 'lks-note-src' }, n.source),\n createElement('div', { className: 'lks-note-title' }, n.title),\n createElement('div', { className: 'lks-note-text' }, n.text),\n n.quote !== null ? createElement('div', { className: 'lks-note-q' }, `“${n.quote}”`) : null,\n )),\n )),\n ),\n )\n return createElement('div', { className: 'lks-col lks-col-bb' },\n createElement('div', { className: 'lks-colhead' }, '黑板'),\n body,\n )\n}\n","/**\n * The one-click study starter: a pill button in the blank-session (hero)\n * composer tool row. One click adopts the plugin's dedicated study workspace\n * (created by the host beside the state file), opens a session in it, sends\n * the kickoff prompt through the session API, and makes that session current\n * — the learner lands in the chat with the tutor already responding, one\n * click away from the 学习 tab (view switching is ui-conversation-private,\n * not reachable from an additive plugin).\n * @module dsh-plugin-lookatstudy/client/starter\n */\n\nimport { createElement, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport { IconLoadingOutline16, IconThinkOutline16 } from './icons.tsx'\nimport { useStudy } from './data.ts'\nimport type { InputZone } from '@deepseek-ai/dsh-client-ui-conversation/client'\nimport type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'\n\n/** The kickoff prompt: starts the tutoring loop and points at the study tab. */\nconst KICKOFF = '开始学习:查看我的学习状态并打开当前焦点课时。如果我还没有课程,推荐我导入示例课程(AI-For-Beginners)并说明怎么开始。回复最后请提醒我点上方「学习」页签进入学习界面。'\n\n/**\n * Build the hero starter button bound to the framework services.\n * @param ctx - client root context (workspaces + sessions injected).\n * @returns the component for `conversation.input.left`.\n */\nexport function studyStartButton(ctx: ClientContext): (props: InputZone) => ReactNode {\n return function StudyStartButton({ session }: InputZone): ReactNode {\n if (session.blank !== true) return null\n return createElement(Inner, { key: 'inner', ctx })\n }\n}\n\n/** Button body in its own component so hook order stays constant. */\nfunction Inner({ ctx }: { ctx: ClientContext }): ReactNode {\n const { data, activate } = useStudy()\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState<string | null>(null)\n const start = (): void => {\n if (busy) return\n setBusy(true)\n setError(null)\n void (async () => {\n try {\n // Dormant installs activate first: the host registers the study tools\n // before this POST resolves, so the kickoff prompt below meets a model\n // that can already call them.\n if (data?.active !== true) await activate(true)\n const area = await fetch('/lookatstudy/api/study-workspace')\n if (!area.ok) throw new Error(`study area unavailable (HTTP ${area.status})`)\n const { path } = await area.json() as { path: string }\n const workspace = await ctx.workspaces.create({ path })\n const sessionId = await ctx.workspaces.connectWorkspace(workspace.workspaceId)\n const actx = ctx.sessions.scope(sessionId)\n const face = actx === undefined ? undefined : ctx.sessions.sessionOf(actx)\n if (face === undefined) throw new Error('study session is not addressable yet')\n const result = await face.prompt([{ type: 'text', text: KICKOFF }], 'queue')\n if (!result.ok) throw new Error(`kickoff prompt rejected: ${result.error.code}: ${result.error.message}`)\n ctx.sessions.open(sessionId)\n } catch (err) {\n setError(err instanceof Error ? err.message : String(err))\n setBusy(false)\n }\n })()\n }\n return createElement('span', { className: 'lks-root', style: { display: 'inline-flex', alignItems: 'center', gap: '8px' } },\n createElement('button', {\n className: 'lks-pill on',\n title: '一键准备学习:建立学习工作区、开启会话并让导师就位',\n disabled: busy,\n onClick: start,\n },\n busy\n ? createElement(IconLoadingOutline16, { className: 'lks-spin' })\n : createElement(IconThinkOutline16, null),\n busy ? '正在准备学习区…' : data?.active === true ? '进入学习' : '开始学习'),\n error !== null ? createElement('span', { className: 'lks-propcard-err', style: { marginTop: 0 } }, error) : null,\n )\n}\n","/**\n * Browser half of dsh-plugin-lookatstudy (`dsh.client`): ONE conversation\n * view tab — 「学习」— carrying the whole plugin. Inside the tab it is a\n * simplified LookatStudy in three columns (课程 | 老师 | 黑板); nothing\n * outside the tab modifies dsh chrome. Live study state comes from the shared\n * poll store over the host plugin's `/lookatstudy/api/*` routes; the tutor\n * column additionally reads the session snapshot through the framework\n * standard kit and sends messages via the same reverse channel as the\n * standalone workbench page.\n * @module dsh-plugin-lookatstudy/client\n */\n\nimport type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'\nimport { ensureStudyStyles } from './styles.ts'\nimport { studyView } from './views.tsx'\nimport { studyStartButton } from './starter.tsx'\n\nexport { studyView, transcriptRows } from './views.tsx'\nexport type { ChatRow } from './views.tsx'\n\nexport const inject = ['slots', 'workspaces', 'sessions']\n\n/**\n * Register the study surfaces: styles inject once; the single\n * `conversation.view` tab carries the whole plugin; the hero starter button\n * (`conversation.input.left`, blank sessions only) automates the onboarding —\n * study workspace + session + kickoff prompt in one click.\n * @param ctx - client root context.\n */\nexport function apply(ctx: ClientContext): void {\n ensureStudyStyles()\n ctx.slots.inject('conversation.view', () => ctx.slots.register(\n { name: 'conversation.view', id: 'lookatstudy-study', order: 15, label: '学习' },\n studyView(ctx),\n ))\n ctx.slots.inject('conversation.input.left', () => ctx.slots.register(\n { name: 'conversation.input.left', id: 'lookatstudy-start', order: 10 },\n studyStartButton(ctx),\n ))\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":["createElement","useSyncExternalStore","useState","createElement","useRef","createElement","useState"],"sources":["../src/client/styles.ts","../src/client/icons.tsx","../src/client/data.ts","../src/markdown.ts","../src/client/views.tsx","../src/client/starter.tsx","../src/client/index.ts"],"sourcesContent":["/**\n * Style injection for the study tab: one `<style>` element of `.lks-*`\n * classes authored against the dsh `--dsw-*` design tokens, so the tab\n * follows the app's theme (light/dark) instead of carrying its own palette.\n * CSS Modules are unavailable to patch-layer bundles, so the stylesheet is\n * injected once at client-plugin apply; the `data-` attribute keeps the\n * injection idempotent.\n * @module dsh-plugin-lookatstudy/client/styles\n */\n\n/** Marker attribute proving the stylesheet is already in the document. */\nconst STYLE_ID = 'data-dsh-plugin-lookatstudy'\n\n/**\n * The study tab's stylesheet. Class names are `lks-*` namespaced; colors,\n * fonts, and radii come from `--dsw-*` tokens wherever the concept exists.\n */\nconst CSS = `\n.lks-root{font-family:var(--dsw-font-family)}\n.lks-root :where(button){font-family:var(--dsw-font-family);cursor:pointer;border:none;background:none;padding:0}\n.lks-root :where(a){color:var(--dsw-alias-state-business-primary)}\n\n/* ── the study tab: composer-width center, self-scrolling flanks ──\n data-conversation-composer-overlay (set on the root element) opts the\n view into the host's overlay mode: viewArea becomes a fixed-height\n non-growing host and the composer floats over the tab's bottom band, so\n the tab itself never feeds the page scroll. The bottom padding clears the\n floating composer via the host-published live --dsh-composer-height. The\n center strip reuses the conversation column's width axis\n (--dsh-composer-card-max-width inherits from ConversationRoot's root), so\n the tutor transcript is exactly as wide as the native composer; the\n flanking columns absorb the remaining width and scroll independently. */\n.lks-study{height:100%;min-height:0;width:100%;box-sizing:border-box;overflow:hidden;display:flex;flex-direction:column;padding:10px 14px calc(var(--dsh-composer-height, 152px) + 12px);color:var(--dsw-alias-label-primary);container:lksstudy/inline-size}\n/* Direction carrier (a container query cannot style the container itself). */\n.lks-body{flex:1;min-height:0;min-width:0;display:flex;gap:16px}\n.lks-col{display:flex;flex-direction:column;min-width:0}\n.lks-colhead{flex:none;font-size:12px;font-weight:600;color:var(--dsw-alias-label-tertiary);letter-spacing:.06em;text-transform:uppercase;margin-bottom:10px}\n.lks-col-rail{flex:0 1 260px;min-width:180px;overflow-y:auto;padding:2px 6px}\n.lks-col-tutor{flex:0 0 auto;width:min(100%,var(--dsh-composer-card-max-width,780px))}\n.lks-col-bb{flex:1 1 0;min-width:230px;overflow-y:auto;padding:2px 6px}\n/* While the study view is mounted in wide mode, the host composer card is\n shifted under the tutor column (the transcript's width axis) instead of the\n scroll body's center — views.tsx measures the offset, sets the variable and\n toggles the class, and removes both whenever the shift collapses (narrow\n pane, hidden view) so the host's natural centering comes back. */\n[data-composer-card].lks-composer-follow{align-self:flex-start;margin-left:var(--lks-composer-shift,0px)}\n/* narrow (<1220px): one composer-width pane at a time, chosen by a centered\n segmented pill group (joined buttons in one capsule — button language, not\n tab language, against the host's view tabs right above). */\n.lks-switch{display:none;flex-direction:row;gap:2px;align-self:center;margin-bottom:10px;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:999px;padding:3px}\n.lks-switch-btn{border-radius:999px;padding:5px 18px;font-size:14px;color:var(--dsw-alias-label-tertiary);background:transparent;transition:background .12s ease,color .12s ease}\n.lks-switch-btn:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}\n.lks-switch-btn.on{background:var(--dsw-alias-state-business-primary);color:#fff;font-weight:600}\n@container lksstudy (max-width: 1220px){\n .lks-body{flex-direction:column}\n .lks-switch{display:flex}\n .lks-colhead{display:none}\n .lks-col{flex:1 1 auto;min-height:0;width:auto;overflow-y:auto}\n .lks-col-tutor{width:min(100%,var(--dsh-composer-card-max-width,780px));overflow:hidden;display:flex;flex-direction:column}\n .lks-study[data-pane='rail'] .lks-col-tutor,.lks-study[data-pane='rail'] .lks-col-bb,\n .lks-study[data-pane='tutor'] .lks-col-rail,.lks-study[data-pane='tutor'] .lks-col-bb,\n .lks-study[data-pane='bb'] .lks-col-rail,.lks-study[data-pane='bb'] .lks-col-tutor{display:none}\n}\n\n/* ── left column: course rail ── */\n.lks-rail-head{display:flex;align-items:center;gap:6px;margin-bottom:4px}\n.lks-rail-head .lks-rail-select{flex:1;min-width:0;margin-bottom:0}\n.lks-rail-head .lks-btn{padding:3px 8px;font-size:13px}\n.lks-rail-title{font-size:16px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.lks-rail-sub{font-size:13px;color:var(--dsw-alias-label-tertiary);margin:4px 0 6px}\n.lks-masterybar{height:5px;border-radius:3px;background:var(--dsw-alias-bg-layer-3);overflow:hidden;margin-bottom:8px}\n.lks-masterybar i{display:block;height:100%;background:var(--dsw-alias-state-business-primary);transition:width .3s ease}\n.lks-masterybar.gold i{background:var(--dsw-alias-state-warn-primary)}\n.lks-search{width:100%;box-sizing:border-box;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;color:var(--dsw-alias-label-primary);font-family:inherit;font-size:13.5px;padding:5px 9px;margin-bottom:4px}\n.lks-search:focus{outline:none;border-color:var(--dsw-alias-state-business-primary);box-shadow:0 0 0 2px var(--dsw-alias-border-l3)}\n.lks-sec-num{display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;border-radius:50%;background:var(--dsw-alias-state-warn-tertiary);color:var(--dsw-alias-state-warn-label);font-size:11px;font-weight:700;margin-right:4px}\n.lks-tag.due{background:var(--dsw-alias-state-warn-tertiary);color:var(--dsw-alias-state-warn-label)}\n.lks-import{margin-top:8px}\n.lks-import .lks-inputrow{margin-top:0}\n.lks-inputrow{display:flex;gap:8px;align-items:flex-end;flex:none;margin-top:8px}\n.lks-input{flex:1;min-width:0;resize:none;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;color:var(--dsw-alias-label-primary);font-family:inherit;font-size:14px;line-height:1.6;padding:8px 10px}\n.lks-input:focus{outline:none;border-color:var(--dsw-alias-state-business-primary)}\n.lks-input:disabled{opacity:.55}\n.lks-import-hint{font-size:12.5px;color:var(--dsw-alias-label-tertiary);margin-top:6px;line-height:1.7}\n.lks-duebox{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:10px;padding:10px 12px;margin:8px 0 14px;font-size:13.5px}\n.lks-duebox .lks-due-item{color:var(--dsw-alias-label-secondary);margin-top:4px;display:flex;justify-content:space-between;gap:10px}\n.lks-duebox .lks-over{color:var(--dsw-alias-state-warn-label);flex:none}\n\n.lks-sec{font-size:12px;color:var(--dsw-alias-label-tertiary);text-transform:uppercase;letter-spacing:.05em;margin:14px 0 4px}\n/* Collapsible section head: a real button (keyboard can toggle it), sticky so\n the current chapter stays identified while 4000px of nodes scroll under it. */\n.lks-sechead{display:flex;align-items:center;gap:6px;width:100%;box-sizing:border-box;font-size:12px;font-weight:600;color:var(--dsw-alias-label-tertiary);text-transform:uppercase;letter-spacing:.05em;text-align:left;padding:6px 4px;margin:14px 0 4px;border-radius:6px;position:sticky;top:0;z-index:1;background:var(--dsw-alias-bg-base)}\n.lks-sechead:hover{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover)}\n.lks-sechead:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-border-l3);outline:none}\n.lks-sechead-t{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.lks-sechead-c{flex:none;font-size:10px}\n.lks-sechead-n{flex:none;font-size:11px;text-transform:none;letter-spacing:0}\n.lks-node{display:flex;align-items:center;gap:8px;width:100%;text-align:left;padding:6px 9px;border-radius:8px;margin:1px 0;cursor:pointer}\n.lks-node:hover{background:var(--dsw-alias-interactive-bg-hover)}\n.lks-node:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-border-l3);outline:none}\n.lks-node.focus{background:var(--dsw-alias-bg-layer-3);outline:1px solid var(--dsw-alias-border-l2)}\n.lks-node .lks-g{width:18px;text-align:center;flex:none}\n.lks-node .lks-t{flex:1;font-size:14px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n/* aria-disabled (not the disabled attribute): the row stays focusable so the\n status tooltip can explain WHY it is locked — Firefox drops titles on truly\n disabled form controls. */\n.lks-node[aria-disabled='true']{opacity:.45;cursor:not-allowed}\n.lks-node[aria-disabled='true']:hover{background:none}\n.lks-tag{font-size:11px;border-radius:4px;padding:0 4px;flex:none}\n.lks-tag.weak{background:var(--dsw-alias-state-warn-tertiary);color:var(--dsw-alias-state-warn-label)}\n.lks-tag.fric{background:var(--dsw-alias-state-error-primary);color:#fff}\n.lks-bar{width:44px;height:4px;border-radius:2px;background:var(--dsw-alias-bg-layer-3);overflow:hidden;flex:none}\n.lks-bar i{display:block;height:100%;background:var(--dsw-alias-state-success-primary)}\n.lks-pct{font-size:12px;color:var(--dsw-alias-label-tertiary);flex:none;width:32px;text-align:right}\n\n.lks-empty{color:var(--dsw-alias-label-tertiary);text-align:center;padding:48px 8px;font-size:14px;line-height:2}\n.lks-actbar{display:flex;flex-direction:row;justify-content:flex-end;flex:none;padding-bottom:8px}\n\n/* ── middle column: the tutor ── */\n.lks-transcript{flex:1;min-height:0;overflow-y:auto;display:flex;flex-direction:column;gap:10px;padding:4px 2px}\n.lks-msg{max-width:94%;font-size:16px;line-height:1.7;border-radius:12px;padding:8px 12px;word-break:break-word}\n.lks-msg.user{align-self:flex-end;white-space:pre-wrap;background:var(--dsw-alias-state-business-tertiary);color:var(--dsw-alias-label-primary-bluish)}\n.lks-msg.assistant{align-self:flex-start;background:var(--dsw-alias-bg-layer-2)}\n.lks-msg.streaming{opacity:.7}\n.lks-msg.thinking{align-self:flex-start;display:inline-flex;align-items:center;gap:8px;font-size:13px;color:var(--dsw-alias-label-tertiary);background:none;border-radius:0;padding:2px}\n.lks-msg.tool{align-self:flex-start;font-size:12px;color:var(--dsw-alias-label-tertiary);background:none;border-radius:0;padding:1px 2px}\n.lks-msg.tool.ok{color:var(--dsw-alias-state-success-primary)}\n.lks-msg.tool.bad{color:var(--dsw-alias-state-error-primary)}\n.lks-msg.error{align-self:stretch;color:var(--dsw-alias-state-error-primary);font-size:13px;background:none;border-radius:0;padding:1px 2px}\n/* quiz-taking surface: the tutor's A–D options become clickable answers on the last settled reply */\n.lks-turn{display:flex;flex-direction:column;align-items:flex-start;max-width:94%}\n.lks-quiz{display:flex;flex-direction:column;gap:6px;width:100%;margin:2px 0 4px}\n.lks-opt{display:flex;align-items:baseline;gap:10px;text-align:left;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);border-radius:10px;padding:8px 14px;font-size:14px;line-height:1.5;color:var(--dsw-alias-label-secondary);cursor:pointer;font-family:inherit;transition:border-color .12s ease,color .12s ease,background .12s ease}\n.lks-opt:hover{border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-label-primary);background:var(--dsw-alias-state-business-tertiary)}\n.lks-opt:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-border-l3);outline:none}\n.lks-optletter{font-weight:700;color:var(--dsw-alias-label-primary-bluish);flex:none}\n\n/* soul pills (native tool-row trigger language: 28px transparent, tinted active) */\n.lks-pills{display:inline-flex;align-items:center;height:28px}\n.lks-pill{display:inline-flex;align-items:center;gap:6px;height:28px;padding:0 10px;border:none;border-radius:24px;background:transparent;color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:20px;font-weight:500;transition:background .12s ease,color .12s ease}\n.lks-pill:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}\n.lks-pill:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-border-l3);outline:none}\n.lks-pill.on{background:var(--dsw-alias-state-business-tertiary);color:var(--dsw-alias-label-primary-bluish)}\n.lks-pill.on:hover{background:var(--dsw-alias-state-business-tertiary);color:var(--dsw-alias-label-primary-bluish)}\n\n/* ic_ds_* glyph carriers + busy spinner (native .8s linear spin) */\n@keyframes lks-spin{to{transform:rotate(360deg)}}\n.lks-spin{animation:lks-spin .8s linear infinite}\n\n/* starter chips */\n.lks-dock{display:flex;align-items:center;flex-wrap:wrap;gap:6px;padding:0 2px 6px;flex:none}\n.lks-starter{border:1px solid var(--dsw-alias-border-l2);border-radius:999px;padding:4px 12px;font-size:13px;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2);transition:border-color .12s ease,color .12s ease}\n.lks-starter:hover{border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-label-primary)}\n\n/* proposal banner + shared buttons */\n/* A mastery proposal is a positive, informational moment — business tint, not\n the warn tint it previously wore (a reviewer flagged the semantic mismatch). */\n.lks-banner{display:flex;align-items:center;gap:10px;background:var(--dsw-alias-state-business-tertiary);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;padding:10px 14px;margin:8px 0;font-size:14px;flex:none}\n.lks-banner .lks-why{flex:1;color:var(--dsw-alias-label-secondary)}\n.lks-btn{display:inline-flex;align-items:center;gap:6px;border-radius:8px;padding:6px 14px;font-size:13.5px;font-weight:600;flex:none}\n.lks-btn.primary{background:var(--dsw-alias-state-business-primary);color:#fff}\n.lks-btn.primary:hover{filter:brightness(1.1)}\n.lks-btn.primary:disabled{opacity:.5;cursor:default;filter:none}\n.lks-btn.ghost{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);border:1px solid var(--dsw-alias-border-l2)}\n.lks-btn.ghost:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l3)}\n\n/* ── right column: blackboard ── */\n.lks-lessonhead h2{font-size:18px;margin:0 0 4px;font-weight:700}\n.lks-lessonhead .lks-meta{color:var(--dsw-alias-label-tertiary);font-size:13px;margin-bottom:4px}\n.lks-chips{display:flex;flex-wrap:wrap;gap:6px;margin:8px 0}\n.lks-chip{border-radius:999px;padding:2px 10px;font-size:12.5px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary)}\n.lks-chip.weak{background:var(--dsw-alias-state-warn-tertiary);color:var(--dsw-alias-state-warn-label)}\n\n/* rendered markdown (host-sanitized lesson bodies and assistant replies) */\n.lks-prose{font-size:16px;line-height:1.75}\n.lks-prose h1,.lks-prose h2,.lks-prose h3,.lks-prose h4{margin:14px 0 6px;line-height:1.4}\n.lks-prose p{margin:6px 0}\n.lks-prose pre{background:var(--dsw-alias-markdown-code-block);border:1px solid var(--dsw-alias-border-l1);border-radius:10px;padding:10px;overflow-x:auto;margin:8px 0;font-size:13.5px;font-family:var(--dsw-font-markdown-code-block-small)}\n.lks-prose code{font-family:var(--dsw-font-markdown-code);background:var(--dsw-alias-bg-layer-3);border-radius:4px;padding:1px 5px;font-size:.92em}\n.lks-prose pre code{background:none;padding:0}\n.lks-prose table{border-collapse:collapse;margin:8px 0}\n.lks-prose th,.lks-prose td{border:1px solid var(--dsw-alias-border-l2);padding:5px 11px;font-size:14px}\n.lks-prose th{background:var(--dsw-alias-bg-layer-2)}\n.lks-prose blockquote{border-left:3px solid var(--dsw-alias-state-business-primary);padding:2px 12px;color:var(--dsw-alias-label-secondary);margin:8px 0}\n.lks-prose ul,.lks-prose ol{padding-left:22px;margin:6px 0}\n.lks-prose img{max-width:100%}\n\n/* Cornell notes zones */\n.lks-bb-notes{margin-top:14px;padding-top:10px;border-top:1px solid var(--dsw-alias-border-l1)}\n.lks-zone{margin-bottom:14px}\n.lks-zone h4{font-size:14px;color:var(--dsw-alias-label-secondary);margin:0 0 8px;font-weight:600}\n.lks-note{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);border-radius:10px;padding:10px 14px;margin-bottom:8px}\n.lks-note .lks-note-src{float:right;font-size:11.5px;color:var(--dsw-alias-label-secondary)}\n.lks-note .lks-note-title{font-weight:600;font-size:13px}\n.lks-note .lks-note-text{margin-top:4px;white-space:pre-wrap;font-size:13px;color:var(--dsw-alias-label-secondary)}\n.lks-note .lks-note-q{margin-top:6px;color:var(--dsw-alias-label-tertiary);font-size:12px;border-left:2px solid var(--dsw-alias-state-warn-primary);padding-left:8px}\n\n/* inline error text (write-action failures) */\n.lks-propcard-err{color:var(--dsw-alias-state-error-primary);font-size:12px;margin-top:6px;flex:none}\n`\n\n/**\n * Inject the study stylesheet into `document.head` (idempotent).\n * @returns the stylesheet element (existing one when already injected).\n */\nexport function ensureStudyStyles(): HTMLStyleElement {\n const existing = document.head.querySelector(`style[${STYLE_ID}]`)\n if (existing !== null) return existing\n const style = document.createElement('style')\n style.setAttribute(STYLE_ID, '')\n style.textContent = CSS\n document.head.appendChild(style)\n return style\n}\n","/**\n * ic_ds_* glyphs vendored from @deepseek-ai/dsh-client-ui-primitives\n * (src/icons/index.tsx, same figma source as the host's icon set) so the\n * plugin's client bundle stays self-contained per the dsh client-bundle\n * contract — only react and dsh-client-runtime/client are runtime-external;\n * importing the host package root would drag its CSS modules into the\n * inlined bundle. Re-sync these three paths if the host icon set redesigns\n * them; scripts/verify.mjs pins the path data.\n * @module dsh-plugin-lookatstudy/client/icons\n */\n\nimport { createElement } from 'react'\n\n/** Shared props for every vendored glyph (mirrors the host's IconProps). */\ninterface IconProps {\n /** Square edge in px; defaults to the glyph's own drawn size. */\n size?: number\n /** Extra class for layout placement; color rides currentColor. */\n className?: string\n}\n\n/** ic_ds_think_outline_16 */\nexport function IconThinkOutline16({ size = 16, className }: IconProps) {\n return createElement('svg', {\n width: size, height: size, className, viewBox: '0 0 16 16', fill: 'none', xmlns: 'http://www.w3.org/2000/svg',\n },\n createElement('path', {\n d: 'M8.00192 6.64454C8.75026 6.64454 9.35732 7.25169 9.35739 8.00001C9.35739 8.74838 8.7503 9.35548 8.00192 9.35548C7.25367 9.35533 6.64743 8.74829 6.64743 8.00001C6.6475 7.25178 7.25371 6.64468 8.00192 6.64454Z',\n fill: 'currentColor',\n }),\n createElement('path', {\n 'fill-rule': 'evenodd',\n 'clip-rule': 'evenodd',\n d: 'M9.97165 1.29981C11.5853 0.718916 13.271 0.642197 14.3144 1.68555C15.3577 2.72902 15.2811 4.41466 14.7002 6.02833C14.4707 6.66561 14.1504 7.32937 13.75 8.00001C14.1504 8.67062 14.4707 9.33444 14.7002 9.97169C15.2811 11.5854 15.3578 13.271 14.3144 14.3145C13.271 15.3579 11.5854 15.2811 9.97165 14.7002C9.3344 14.4708 8.67059 14.1505 7.99997 13.75C7.32933 14.1505 6.66558 14.4708 6.02829 14.7002C4.41461 15.2811 2.72899 15.3578 1.68552 14.3145C0.642155 13.271 0.71887 11.5854 1.29977 9.97169C1.52915 9.33454 1.84865 8.67049 2.24899 8.00001C1.84866 7.32953 1.52915 6.66544 1.29977 6.02833C0.718852 4.41459 0.64207 2.729 1.68552 1.68555C2.72897 0.642112 4.41456 0.718887 6.02829 1.29981C6.66541 1.52918 7.32949 1.8487 7.99997 2.24903C8.67045 1.84869 9.33451 1.52919 9.97165 1.29981ZM12.9404 9.2129C12.4391 9.893 11.8616 10.5681 11.2148 11.2149C10.568 11.8616 9.89296 12.4391 9.21286 12.9404C9.62532 13.1579 10.0271 13.338 10.4121 13.4766C11.9146 14.0174 12.9172 13.8738 13.3955 13.3955C13.8737 12.9173 14.0174 11.9146 13.4765 10.4121C13.3379 10.0271 13.1578 9.62535 12.9404 9.2129ZM3.05856 9.2129C2.84121 9.62523 2.66197 10.0272 2.52341 10.4121C1.98252 11.9146 2.12627 12.9172 2.60446 13.3955C3.08278 13.8737 4.08544 14.0174 5.58786 13.4766C5.97264 13.338 6.37389 13.1577 6.7861 12.9404C6.10624 12.4393 5.43168 11.8614 4.78513 11.2149C4.13823 10.5679 3.55992 9.89313 3.05856 9.2129ZM7.99899 3.792C7.23179 4.31419 6.45306 4.95512 5.70407 5.70411C4.95509 6.45309 4.31415 7.23184 3.79196 7.99903C4.3143 8.76666 4.95471 9.54653 5.70407 10.2959C6.45309 11.0449 7.23271 11.6848 7.99997 12.207C8.76725 11.6848 9.54683 11.0449 10.2959 10.2959C11.0449 9.54686 11.6848 8.76729 12.207 8.00001C11.6848 7.23275 11.0449 6.45312 10.2959 5.70411C9.5465 4.95475 8.76662 4.31434 7.99899 3.792ZM5.58786 2.52344C4.08533 1.98255 3.08272 2.12625 2.60446 2.6045C2.12621 3.08275 1.98252 4.08536 2.52341 5.5879C2.66189 5.97253 2.8414 6.37409 3.05856 6.78614C3.55983 6.10611 4.1384 5.43189 4.78513 4.78516C5.43186 4.13843 6.10606 3.55987 6.7861 3.0586C6.37405 2.84144 5.97249 2.66192 5.58786 2.52344ZM13.3955 2.6045C12.9172 2.12631 11.9146 1.98257 10.4121 2.52344C10.0272 2.66201 9.62519 2.84125 9.21286 3.0586C9.8931 3.55996 10.5679 4.13827 11.2148 4.78516C11.8614 5.43172 12.4392 6.10627 12.9404 6.78614C13.1577 6.37393 13.338 5.97267 13.4765 5.5879C14.0174 4.08549 13.8736 3.08281 13.3955 2.6045Z',\n fill: 'currentColor',\n }))\n}\n\n/** ic_ds_loading_outline_16 */\nexport function IconLoadingOutline16({ size = 16, className }: IconProps) {\n return createElement('svg', {\n width: size, height: size, className, viewBox: '0 0 16 16', fill: 'none', xmlns: 'http://www.w3.org/2000/svg',\n },\n createElement('path', {\n d: 'M2.871 13.1286C0.0387669 10.2962 0.0387669 5.70383 2.871 2.87141C5.70341 0.0390029 10.2957 0.0391154 13.1282 2.87141L12.1387 3.86094C9.85292 1.57538 6.1469 1.57596 3.86123 3.86163C1.57573 6.14732 1.57573 9.85269 3.86123 12.1384C6.1469 14.424 9.85292 14.4246 12.1387 12.1391L13.1282 13.1286C10.2957 15.9609 5.70341 15.961 2.871 13.1286Z',\n fill: 'currentColor',\n }))\n}\n\n/** ic_ds_download_outline_16 */\nexport function IconDownloadOutline16({ size = 16, className }: IconProps) {\n return createElement('svg', {\n width: size, height: size, className, viewBox: '0 0 16 16', fill: 'none', xmlns: 'http://www.w3.org/2000/svg',\n },\n createElement('path', {\n d: 'M15.3695 11.411L15.1234 12.8866C14.8869 14.3042 13.6603 15.3436 12.223 15.3436H3.77673C2.33958 15.3434 1.1128 14.3042 0.876343 12.8866L0.630249 11.411L2.05408 11.1747L2.29919 12.6493C2.41973 13.3713 3.04475 13.9001 3.77673 13.9003H12.223C12.9551 13.9002 13.58 13.3713 13.7006 12.6493L13.9457 11.1747L15.3695 11.411ZM8.72205 8.994C8.77717 8.93934 8.83792 8.88106 8.90271 8.81627L12.4828 5.23424L13.5043 6.25572L9.92224 9.8358C9.6395 10.1185 9.38763 10.3732 9.15857 10.5575C8.91892 10.7503 8.63953 10.9224 8.2865 10.9784C8.09711 11.0083 7.90363 11.0083 7.71423 10.9784C7.36106 10.9224 7.0809 10.7503 6.84119 10.5575C6.61215 10.3732 6.36022 10.1185 6.07751 9.8358L2.49646 6.25572L3.51697 5.23424L7.09705 8.81627C7.16219 8.88142 7.22331 8.94006 7.27869 8.99498V1.3065H8.72205V8.994Z',\n fill: 'currentColor',\n }))\n}\n","/**\n * Shared client-side study store: one module-level poller over the host\n * plugin's `/lookatstudy/api/state` endpoint, plus the three write actions\n * (focus, mode, reverse message). Every seat component binds through\n * {@link useStudy}; subscribers share the single 3 s poll cycle and the last\n * snapshot, so mounting more controls costs no extra requests.\n * @module dsh-plugin-lookatstudy/client/data\n */\n\nimport { useSyncExternalStore } from 'react'\n\n/** Wire shape of `GET /lookatstudy/api/state` (structural mirror of the host's WorkbenchState). */\nexport interface StudyState {\n readonly active: boolean\n readonly mode: 'direct' | 'guide' | 'practice'\n readonly courses: ReadonlyArray<{\n readonly courseId: string\n readonly title: string\n readonly mastered: number\n readonly total: number\n readonly avgMasteryPct: number | null\n readonly sections: ReadonlyArray<{\n readonly title: string\n readonly index: number\n readonly lessons: ReadonlyArray<{\n readonly id: string\n readonly title: string\n readonly kind: 'study' | 'practice' | 'exam'\n readonly status: string\n readonly masteryPct: number | null\n readonly weakConcepts: number\n readonly frictionCount: number\n readonly due: boolean\n readonly focus: boolean\n }>\n }>\n }>\n readonly focusLessonId: string | null\n readonly lesson: {\n readonly lessonId: string\n readonly courseTitle: string\n readonly sectionTitle: string\n readonly title: string\n readonly status: string\n readonly masteryPct: number | null\n readonly strategy: string\n readonly concepts: ReadonlyArray<{ title: string; masteryPct: number; weak: boolean }>\n readonly starters: ReadonlyArray<{ label: string; message: string }>\n readonly notes: ReadonlyArray<{ id: string; zone: string; title: string; text: string; source: string; quote: string | null }>\n readonly html: string\n } | null\n readonly dueCount: number\n readonly due: ReadonlyArray<{ lessonId: string; lessonTitle: string; courseTitle: string; overdueDays: number }>\n readonly pendingProposals: ReadonlyArray<{ id: string; lessonTitle: string; rationale: string }>\n readonly memory: { global: string | null; lesson: string | null; pattern: string | null }\n readonly lessonSessions: Readonly<Record<string, string>>\n}\n\n/** Poll cadence for the shared store; one cycle serves every mounted seat. */\nconst POLL_MS = 3_000\n\ntype Listener = () => void\n\nasync function fetchJson(url: string, init?: RequestInit): Promise<unknown> {\n const res = await fetch(url, init)\n const body: unknown = await res.json().catch(() => null)\n if (!res.ok) {\n const error = body !== null && typeof body === 'object' && 'error' in body && typeof body.error === 'string'\n ? body.error\n : `HTTP ${res.status}`\n throw new Error(error)\n }\n return body\n}\n\nclass StudyStore {\n private snapshot: StudyState | null = null\n private readonly listeners = new Set<Listener>()\n private timer: ReturnType<typeof setInterval> | undefined\n private inflight = false\n\n subscribe = (listener: Listener): (() => void) => {\n this.listeners.add(listener)\n if (this.listeners.size === 1) {\n void this.tick()\n this.timer = setInterval(() => { void this.tick() }, POLL_MS)\n }\n return () => {\n this.listeners.delete(listener)\n if (this.listeners.size === 0 && this.timer !== undefined) {\n clearInterval(this.timer)\n this.timer = undefined\n }\n }\n }\n\n getSnapshot = (): StudyState | null => this.snapshot\n\n private emit(): void {\n for (const listener of this.listeners) listener()\n }\n\n private async tick(): Promise<void> {\n if (this.inflight) return\n this.inflight = true\n try {\n const next = await fetchJson('/lookatstudy/api/state') as StudyState\n this.snapshot = next\n this.emit()\n } catch {\n // Host route absent (headless composition) or transient failure: keep\n // the last snapshot; seats render their loading/empty states.\n } finally {\n this.inflight = false\n }\n }\n\n /** One immediate re-read (after a write action). */\n refresh(): void {\n void this.tick()\n }\n\n /** Switch the tutor's soul mode directly (host persists and applies it on the next request). */\n async setMode(mode: StudyState['mode']): Promise<void> {\n await fetchJson('/lookatstudy/api/mode', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ mode }),\n })\n this.refresh()\n }\n\n /**\n * Flip learning mode. The host syncs its tool registry before responding,\n * so awaiting this guarantees the `study_*` tools exist for the next prompt.\n */\n async activate(active: boolean): Promise<void> {\n await fetchJson('/lookatstudy/api/active', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ active }),\n })\n this.refresh()\n }\n\n /** Point the tutor's focus at one lesson. */\n async setFocus(lessonId: string): Promise<void> {\n await fetchJson('/lookatstudy/api/focus', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ lessonId }),\n })\n this.refresh()\n }\n\n /** Record the dsh session backing one lesson (the simplified thread system). */\n async bindLessonSession(lessonId: string, sessionId: string): Promise<void> {\n await fetchJson('/lookatstudy/api/lesson-session', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ lessonId, sessionId }),\n })\n this.refresh()\n }\n\n /** Delete one course (and its proposals) from the host state. */\n async deleteCourse(courseId: string): Promise<void> {\n await fetchJson('/lookatstudy/api/course/delete', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ courseId }),\n })\n this.refresh()\n }\n}\n\n/** The one shared store instance backing every study seat component. */\nexport const studyStore = new StudyStore()\n\n/** React binding: live snapshot plus the write actions. */\nexport function useStudy(): {\n data: StudyState | null\n activate: (active: boolean) => Promise<void>\n setMode: (mode: StudyState['mode']) => Promise<void>\n setFocus: (lessonId: string) => Promise<void>\n deleteCourse: (courseId: string) => Promise<void>\n bindLessonSession: (lessonId: string, sessionId: string) => Promise<void>\n} {\n const data = useSyncExternalStore(studyStore.subscribe, studyStore.getSnapshot, studyStore.getSnapshot)\n return {\n data,\n activate: studyStore.activate.bind(studyStore),\n setMode: studyStore.setMode.bind(studyStore),\n setFocus: studyStore.setFocus.bind(studyStore),\n deleteCourse: studyStore.deleteCourse.bind(studyStore),\n bindLessonSession: studyStore.bindLessonSession.bind(studyStore),\n }\n}\n","/**\n * Server-side markdown → HTML for the study workbench's 讲解 view. Escapes\n * every HTML character first, then renders a pragmatic GFM subset (headings,\n * fenced code, inline code, bold/italic, links, lists, blockquotes, tables,\n * hr, paragraphs). Lesson bodies are imported teaching material, so raw HTML\n * never passes through.\n * @module dsh-plugin-lookatstudy/markdown\n */\n\n/** Escape all HTML-significant characters. */\nfunction escapeHtml(text: string): string {\n return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;')\n}\n\n/** Render inline markup (code, bold, italic, links) over escaped text. */\nfunction inline(escaped: string): string {\n return escaped\n .replace(/`([^`]+)`/g, '<code>$1</code>')\n .replace(/\\*\\*([^*]+)\\*\\*/g, '<strong>$1</strong>')\n .replace(/\\*([^*]+)\\*/g, '<em>$1</em>')\n .replace(/\\[([^\\]]+)\\]\\((https?:\\/\\/[^)\\s]+)\\)/g, '<a href=\"$2\" target=\"_blank\" rel=\"noreferrer\">$1</a>')\n}\n\n/** True when the line opens a GFM table row (pipes with a delimiter row next). */\nfunction isTableRow(line: string): boolean {\n return line.trim().startsWith('|') && line.trim().endsWith('|') && line.includes('|', 1)\n}\n\nfunction isDelimiterRow(line: string): boolean {\n const trimmed = line.trim()\n if (!trimmed.startsWith('|') || !trimmed.endsWith('|')) return false\n const cells = trimmed.replace(/^\\|/, '').replace(/\\|$/, '').split('|').map(c => c.trim())\n return cells.length > 0 && cells.every(c => /^:?-+:?$/.test(c))\n}\n\n/** Split a table row into trimmed cells (leading/trailing pipes removed). */\nfunction rowCells(line: string): string[] {\n return line.trim().replace(/^\\|/, '').replace(/\\|$/, '').split('|').map(c => c.trim())\n}\n\n/**\n * Render markdown text to sanitized HTML.\n * @param md - markdown source (lesson body).\n * @returns HTML string safe to inject into the page.\n */\nexport function renderMarkdown(md: string): string {\n const lines = escapeHtml(md).split(/\\r?\\n/)\n const out: string[] = []\n let i = 0\n\n const flushParagraph = (buffer: string[]): void => {\n if (buffer.length > 0) out.push(`<p>${inline(buffer.join(' '))}</p>`)\n }\n\n let paragraph: string[] = []\n while (i < lines.length) {\n const line = lines[i]!\n\n // Fenced code block\n const fence = /^```(\\w*)\\s*$/.exec(line.trim())\n if (fence) {\n flushParagraph(paragraph)\n paragraph = []\n const lang = fence[1] ?? ''\n const code: string[] = []\n i++\n while (i < lines.length && lines[i]!.trim() !== '```') {\n code.push(lines[i]!)\n i++\n }\n i++ // closing fence\n out.push(`<pre><code${lang === '' ? '' : ` class=\"lang-${lang}\"`}>${code.join('\\n')}</code></pre>`)\n continue\n }\n\n // Heading\n const heading = /^(#{1,6})\\s+(.*)$/.exec(line)\n if (heading) {\n flushParagraph(paragraph)\n paragraph = []\n const level = heading[1]!.length\n out.push(`<h${level}>${inline(heading[2]!)}</h${level}>`)\n i++\n continue\n }\n\n // Horizontal rule\n if (/^\\s*(-{3,}|\\*{3,}|_{3,})\\s*$/.test(line)) {\n flushParagraph(paragraph)\n paragraph = []\n out.push('<hr>')\n i++\n continue\n }\n\n // Blockquote\n if (/^\\s*&gt;\\s?/.test(line)) {\n flushParagraph(paragraph)\n paragraph = []\n const quote: string[] = []\n while (i < lines.length && /^\\s*&gt;\\s?/.test(lines[i]!)) {\n quote.push(lines[i]!.replace(/^\\s*&gt;\\s?/, ''))\n i++\n }\n out.push(`<blockquote><p>${inline(quote.join(' '))}</p></blockquote>`)\n continue\n }\n\n // Unordered list\n if (/^\\s*[-*+]\\s+/.test(line)) {\n flushParagraph(paragraph)\n paragraph = []\n const items: string[] = []\n while (i < lines.length && /^\\s*[-*+]\\s+/.test(lines[i]!)) {\n items.push(`<li>${inline(lines[i]!.replace(/^\\s*[-*+]\\s+/, ''))}</li>`)\n i++\n }\n out.push(`<ul>${items.join('')}</ul>`)\n continue\n }\n\n // Ordered list\n if (/^\\s*\\d+\\.\\s+/.test(line)) {\n flushParagraph(paragraph)\n paragraph = []\n const items: string[] = []\n while (i < lines.length && /^\\s*\\d+\\.\\s+/.test(lines[i]!)) {\n items.push(`<li>${inline(lines[i]!.replace(/^\\s*\\d+\\.\\s+/, ''))}</li>`)\n i++\n }\n out.push(`<ol>${items.join('')}</ol>`)\n continue\n }\n\n // GFM table\n if (isTableRow(line) && i + 1 < lines.length && isDelimiterRow(lines[i + 1]!)) {\n flushParagraph(paragraph)\n paragraph = []\n const headers = rowCells(line)\n i += 2\n const rows: string[] = []\n while (i < lines.length && isTableRow(lines[i]!)) {\n const cells = rowCells(lines[i]!)\n rows.push(`<tr>${cells.map(c => `<td>${inline(c)}</td>`).join('')}</tr>`)\n i++\n }\n out.push(`<table><thead><tr>${headers.map(h => `<th>${inline(h)}</th>`).join('')}</tr></thead><tbody>${rows.join('')}</tbody></table>`)\n continue\n }\n\n // Blank line ends the paragraph\n if (line.trim() === '') {\n flushParagraph(paragraph)\n paragraph = []\n i++\n continue\n }\n\n paragraph.push(line.trim())\n i++\n }\n flushParagraph(paragraph)\n return out.join('\\n')\n}\n","/**\n * The study tab: one `conversation.view` entry rendering the whole plugin —\n * a simplified LookatStudy inside dsh, three columns in the tab. Left: the\n * course map (selector, tree, due box, one-click demo import). Middle: the\n * tutor conversation — a read-only mini transcript folded from the session\n * snapshot (user text, assistant text rendered through the plugin's own\n * markdown pipeline, tool chips) plus the soul pills, starters, pending\n * proposal banner, and a reverse-channel input. Right: the blackboard —\n * focus-lesson 讲解 and the Cornell 笔记 zones. Nothing outside this tab\n * touches dsh chrome.\n * @module dsh-plugin-lookatstudy/client/views\n */\n\nimport { createElement, useEffect, useRef, useState } from 'react'\nimport type { KeyboardEvent as ReactKeyboardEvent, ReactNode } from 'react'\nimport { IconDownloadOutline16, IconLoadingOutline16 } from './icons.tsx'\nimport type { ConvViewProps } from '@deepseek-ai/dsh-client-ui-conversation/client'\nimport type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'\nimport type {\n AssistantBlock, ConversationNode, ConversationSnapshot, PartialAssistant,\n} from '@deepseek-ai/dsh-client-runtime/client'\nimport { useStudy } from './data.ts'\nimport { renderMarkdown } from '../markdown.ts'\n\n/** The three souls, in pill order (labels from LookatStudy's mode switcher). */\nconst MODES: ReadonlyArray<{ id: 'direct' | 'guide' | 'practice'; label: string; hint: string }> = [\n { id: 'direct', label: '直讲', hint: 'direct 精讲:先讲清楚,再确认懂没懂' },\n { id: 'guide', label: '引导', hint: 'guide 引导:让你自己往前推一步,导师递台阶' },\n { id: 'practice', label: '实战', hint: 'practice 实战:在真实世界的乱问题里学' },\n]\n\n/** Zone labels for the Cornell notebook (understand / record / practice). */\nconst ZONES: ReadonlyArray<readonly [string, string]> = [\n ['understand', '🧠 理解区 — 知识结构'],\n ['record', '📝 记录区 — 我的话'],\n ['practice', '✍️ 练习区 — 答题日志'],\n]\n\n/** Status glyph for one lesson row (LookatStudy's map icons). */\nfunction glyph(kind: string, status: string): string {\n if (kind === 'exam') return '🎯'\n if (status === 'mastered') return '👑'\n if (status === 'in_progress') return '📖'\n if (status === 'available') return '⭐'\n return '🔒'\n}\n\n/** LookatStudy's exam gate: an exam node opens only when every sibling study lesson reached mastery ≥50%. */\nfunction examOpen(lessons: ReadonlyArray<{ kind: string; masteryPct: number | null }>): boolean {\n return lessons.every(l => l.kind !== 'study' || (l.masteryPct ?? 0) >= 50)\n}\n\n/** Multi-keyword AND title filter (LookatStudy course-tree-filter). */\nfunction titleMatches(title: string, query: string): boolean {\n const keys = query.trim().toLowerCase().split(/\\s+/).filter(Boolean)\n return keys.every(k => title.toLowerCase().includes(k))\n}\n\n/**\n * Default expansion for one rail section: collapsed when every study lesson is\n * done (mastered) or not yet reachable (locked) — long courses otherwise scroll\n * 5× their viewport. The active frontier and the focus lesson's section stay\n * open; exam nodes never force a section open (they are gated separately).\n * Pure.\n * @param section - one course section projection with lesson kinds/statuses.\n */\nexport function sectionDefaultOpen(section: { lessons: ReadonlyArray<{ kind: string; status: string; focus: boolean }> }): boolean {\n return section.lessons.some(l => l.focus || (l.kind !== 'exam' && l.status !== 'mastered' && l.status !== 'locked'))\n}\n\n/** The rail's import row: a GitHub URL input plus the paste/folder hints. */\nfunction ImportRow({ send }: { send: StudySend }): ReactNode {\n const [url, setUrl] = useState('')\n return createElement('div', { className: 'lks-import' },\n createElement('div', { className: 'lks-inputrow' },\n createElement('input', {\n className: 'lks-input',\n type: 'url',\n placeholder: 'GitHub 仓库链接,如 microsoft/AI-For-Beginners',\n value: url,\n onChange: (e: { target: { value: string } }) => { setUrl(e.target.value) },\n onKeyDown: (e: ReactKeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'Enter' && url.trim() !== '') send(`导入课程:用 study_import_github 抓取 ${url.trim()}`)\n },\n }),\n createElement('button', {\n className: 'lks-btn primary',\n disabled: url.trim() === '',\n onClick: () => { send(`导入课程:用 study_import_github 抓取 ${url.trim()}`) },\n }, '导入'),\n ),\n createElement('div', { className: 'lks-import-hint' },\n '粘贴 markdown → 说「导入为课程」', createElement('br'),\n '本地文件夹 → 说「导入 D:/path/to/folder」'),\n )\n}\n\n/** One rendered transcript row (pure fold of the conversation snapshot). */\nexport interface ChatRow {\n readonly key: string\n readonly role: 'user' | 'assistant' | 'tool' | 'error' | 'streaming' | 'thinking'\n readonly text: string\n /** The settled tool call head for `tool` rows; drives the learning-aware chip. */\n readonly call?: { name: string; argsRaw: string } | null\n}\n\n/**\n * Tooltip text for one course-tree status glyph. Pure.\n * @param kind - lesson kind ('study' | 'practice' | 'exam').\n * @param status - lesson status ('locked' | 'available' | 'in_progress' | 'mastered').\n */\nexport function statusTitle(kind: string, status: string): string {\n if (kind === 'exam') return '章节测验:本节全部课时掌握度 ≥50% 后开放'\n switch (status) {\n case 'mastered': return '已毕业:掌握度 ≥90%(或你接受了掌握提案)'\n case 'in_progress': return '学习中:已开课,掌握度从 50% 起步'\n case 'available': return '可开始:已解锁,尚未学习'\n default: return '未解锁:先完成前面的课时'\n }\n}\n\n/**\n * Extract the last quiz option block (2–4 consecutive `A. …` `B. …` lines,\n * letters strictly consecutive from A) from an assistant reply — the raw text\n * of the tutor's question becomes the clickable answer surface. Pure.\n * @param text - one assistant reply's markdown.\n * @returns the options of the last valid block, or [] when none.\n */\nexport function quizOptions(text: string): ReadonlyArray<{ letter: string; text: string }> {\n const runs: Array<Array<{ letter: string; text: string }>> = []\n let run: Array<{ letter: string; text: string }> = []\n const flush = (): void => {\n if (run.length >= 2) runs.push(run)\n run = []\n }\n for (const line of text.split('\\n')) {\n const match = /^([A-D])[.、:)]\\s+(.+)$/.exec(line.trim())\n if (match === null) {\n flush()\n continue\n }\n const nextLetter = run.length === 0 ? 'A' : String.fromCharCode(run[run.length - 1]!.letter.charCodeAt(0) + 1)\n if (match[1] === nextLetter) run.push({ letter: match[1], text: match[2]! })\n else {\n flush()\n if (match[1] === 'A') run.push({ letter: match[1], text: match[2]! })\n }\n }\n flush()\n return runs.at(-1) ?? []\n}\n\n/**\n * Learning-aware label for one settled tool call; null keeps the generic\n * 🔧-name chip. Pure.\n * @param name - tool call name.\n * @param argsRaw - the call's raw JSON args.\n */\nexport function toolChipLabel(name: string, argsRaw: string): { label: string; tone: 'ok' | 'bad' } | null {\n if (name === 'study_record_answer') {\n let concept = '未归因'\n let correct = false\n try {\n const args = JSON.parse(argsRaw) as { concept?: unknown; correct?: unknown }\n if (typeof args.concept === 'string' && args.concept !== '') concept = args.concept\n correct = args.correct === true\n } catch { /* malformed argsRaw falls back to the unattributed/incorrect label */ }\n return correct\n ? { label: `✓ 答对 · ${concept}`, tone: 'ok' }\n : { label: `✗ 答错 · ${concept}`, tone: 'bad' }\n }\n if (name === 'study_import_github' || name === 'study_import_markdown' || name === 'study_import_folder') return { label: '📦 导入课程', tone: 'ok' }\n if (name === 'study_define_concepts') return { label: '🧠 提炼知识点', tone: 'ok' }\n return null\n}\n\nfunction textOf(blocks: readonly { type: string; text?: string }[]): string {\n return blocks.filter(b => b.type === 'text').map(b => b.text ?? '').join('\\n\\n').trim()\n}\n\nfunction assistantText(blocks: readonly AssistantBlock[]): string {\n return blocks.filter(b => b.kind === 'text').map(b => b.text).join('\\n\\n').trim()\n}\n\n/**\n * Fold conversation nodes (plus the streaming partial) into render rows;\n * tool activity condenses to one muted chip per settled call.\n * @param nodes - finalized conversation nodes from the snapshot.\n * @param partial - the in-flight assistant partial, or null.\n * @returns ordered rows; never mutates its inputs.\n */\nexport function transcriptRows(nodes: readonly ConversationNode[], partial: PartialAssistant | null): readonly ChatRow[] {\n const rows: ChatRow[] = []\n for (const node of nodes) {\n switch (node.kind) {\n case 'user':\n case 'steering': {\n const text = textOf(node.content)\n if (text !== '') rows.push({ key: `u${node.seq}`, role: 'user', text })\n break\n }\n case 'assistant': {\n const text = assistantText(node.blocks)\n if (text !== '') rows.push({ key: `a${node.seq}`, role: 'assistant', text })\n break\n }\n case 'tool-result':\n rows.push({ key: `t${node.seq}`, role: 'tool', text: node.call?.name ?? node.callId, call: node.call })\n break\n case 'turn-error':\n rows.push({ key: `e${node.seq}`, role: 'error', text: node.message })\n break\n default:\n break\n }\n }\n if (partial !== null) {\n const text = assistantText(partial.blocks)\n if (text !== '') rows.push({ key: 'streaming', role: 'streaming', text })\n // No text yet means the tutor is reasoning (or lining up tool calls) —\n // without this row the column sits dead silent through the whole phase.\n else rows.push({ key: 'thinking', role: 'thinking', text: '导师思考中…' })\n }\n return rows\n}\n\n/** Small inline error surface for failed write actions. */\nfunction ActionError({ error }: { error: string | null }): ReactNode {\n if (error === null) return null\n return createElement('div', { className: 'lks-propcard-err' }, error)\n}\n\n/**\n * Land one message in the dsh composer and submit it — the same path as the\n * Send button, so button text is visible in the native input for a beat and\n * then goes through the full submission pipeline (no reverse channel).\n */\nexport type StudySend = (text: string) => void\n\n/** Narrow-mode pane selector: which of the three columns is the main display. */\ntype StudyPane = 'rail' | 'tutor' | 'bb'\n\n/** sessionStorage key keeping the narrow-mode pane across view remounts (tab switches) and reloads. */\nconst PANE_KEY = 'dsh-plugin-lookatstudy:pane'\n\n/** Narrow the stored value back to a pane id; anything else (or nothing stored) falls back to 导师. Pure, unit-testable. */\nexport function pickPane(stored: string | null): StudyPane {\n return stored === 'rail' || stored === 'tutor' || stored === 'bb' ? stored : 'tutor'\n}\n\n/** Read the persisted pane; the try swallows only the SecurityError privacy modes throw on storage access — the fallback is the default pane. */\nfunction storedPane(): StudyPane {\n try {\n return pickPane(sessionStorage.getItem(PANE_KEY))\n } catch {\n return 'tutor'\n }\n}\n\n/** The whole study tab. Wide = three columns (课程 | 导师 | 黑板); narrow (<1220px) = one composer-width pane with a three-way switcher. */\nexport function studyView(ctx: ClientContext): (props: ConvViewProps) => ReactNode {\n return function StudyView(props: ConvViewProps): ReactNode {\n return createElement(StudyTab, { ...props, ctx, key: 'tab' })\n }\n}\n\n/** Tab body: the factory-bound ctx carries workspaces/sessions for the per-lesson session jumps. */\nfunction StudyTab({ useSession, inputActions, ctx }: ConvViewProps & { ctx: ClientContext }): ReactNode {\n const { data, activate, setMode, setFocus, deleteCourse, bindLessonSession } = useStudy()\n const snapshot = useSession((s: ConversationSnapshot) => s)\n const [pane, setPane] = useState<StudyPane>(storedPane)\n const rootRef = useRef<HTMLDivElement | null>(null)\n // The wide layout puts the tutor column left of the scroll body's center\n // (fixed rail + flexible blackboard), so the floating composer would sit\n // mid-screen under nothing. While this view is mounted and measurable, shift\n // the host composer card ([data-composer-card], a stable host anchor) under\n // the tutor column; any collapsed shift (narrow single-pane mode, hidden\n // view, no room) removes the class and restores the host's natural\n // centering. ResizeObserver on the study root catches mount/show/hide/resize.\n useEffect(() => {\n const root = rootRef.current\n const card = document.querySelector<HTMLElement>('[data-composer-card]')\n const seat = card?.parentElement ?? null\n if (root === null || card === null || seat === null) return\n const clear = (): void => {\n card.classList.remove('lks-composer-follow')\n card.style.removeProperty('--lks-composer-shift')\n }\n const apply = (): void => {\n const tutor = root.querySelector<HTMLElement>('.lks-col-tutor')\n const rail = root.querySelector<HTMLElement>('.lks-col-rail')\n if (tutor === null || rail === null || getComputedStyle(rail).display === 'none') return clear()\n const seatRect = seat.getBoundingClientRect()\n const tutorRect = tutor.getBoundingClientRect()\n if (seatRect.width === 0 || tutorRect.width === 0) return clear()\n const pad = parseFloat(getComputedStyle(seat).paddingLeft) || 0\n const cardW = card.getBoundingClientRect().width\n const max = seatRect.width - 2 * pad - cardW\n const shift = Math.round(Math.max(0, Math.min(tutorRect.left - (seatRect.left + pad), max)))\n if (shift < 2) return clear()\n card.style.setProperty('--lks-composer-shift', `${shift}px`)\n card.classList.add('lks-composer-follow')\n }\n apply()\n const ro = new ResizeObserver(apply)\n ro.observe(root)\n ro.observe(seat)\n window.addEventListener('resize', apply)\n return () => {\n ro.disconnect()\n window.removeEventListener('resize', apply)\n clear()\n }\n }, [])\n const send: StudySend = (text) => {\n void (async () => {\n // Dormant installs activate first: the host registers the study tools\n // before the activation POST resolves, so the prompt meets a model that\n // can already call them.\n if (data?.active !== true) await activate(true)\n inputActions.setDraft(text)\n inputActions.submit()\n })()\n }\n const panes: ReadonlyArray<{ id: StudyPane; label: string }> = [\n { id: 'rail', label: '课程' },\n { id: 'tutor', label: '导师' },\n { id: 'bb', label: '黑板' },\n ]\n return createElement('div', {\n ref: rootRef,\n className: 'lks-root lks-study',\n 'data-conversation-composer-overlay': '',\n 'data-pane': pane,\n },\n // Always-visible activation bar: the tab is the fallback entry when another\n // plugin's composer takeover hides the starter pill.\n createElement('div', { className: 'lks-actbar' },\n data === null ? null : createElement('button', {\n className: `lks-btn ${data.active ? 'ghost' : 'primary'}`,\n title: data.active\n ? '注销 study 工具与导师人格;学习进度保留,可随时重新开启'\n : '注册 study 工具并载入导师人格,之后普通对话和现在一样',\n onClick: () => { void activate(!data.active) },\n }, data.active ? '⏻ 退出学习模式' : '▶ 开始学习'),\n ),\n // .lks-body carries the row/column direction so the container query can\n // flip it — a container query cannot style the container element itself.\n createElement('div', { className: 'lks-body' },\n createElement('div', { className: 'lks-switch' },\n ...panes.map(p => createElement('button', {\n key: p.id,\n className: `lks-switch-btn${pane === p.id ? ' on' : ''}`,\n onClick: () => {\n setPane(p.id)\n try {\n sessionStorage.setItem(PANE_KEY, p.id)\n } catch {\n // Privacy modes forbid storage writes; the in-memory choice still holds this page.\n }\n },\n }, p.label)),\n ),\n createElement(CourseRail, { data, activate, setFocus, deleteCourse, bindLessonSession, send, ctx, currentSessionId: snapshot.sessionId }),\n createElement(TutorColumn, { data, setMode, send, snapshot }),\n createElement(BlackboardColumn, { data }),\n ),\n )\n}\n\ntype StudyData = ReturnType<typeof useStudy>['data']\n\n/** Left column: course management (pick/delete/search/import), lesson tree, due box. */\nfunction CourseRail({ data, activate, setFocus, deleteCourse, bindLessonSession, send, ctx, currentSessionId }: {\n data: StudyData\n activate: (active: boolean) => Promise<void>\n setFocus: (id: string) => Promise<void>\n deleteCourse: (courseId: string) => Promise<void>\n bindLessonSession: (lessonId: string, sessionId: string) => Promise<void>\n send: StudySend\n ctx: ClientContext\n currentSessionId: string\n}): ReactNode {\n const [jumping, setJumping] = useState<string | null>(null)\n /** Open (or mint) the lesson's own session — the simplified thread system. */\n const openLessonThread = (lesson: { id: string; title: string }): void => {\n if (jumping !== null) return\n const mapped = data?.lessonSessions[lesson.id]\n if (mapped === currentSessionId) {\n void setFocus(lesson.id).catch(reportError)\n return\n }\n if (mapped !== undefined && ctx.sessions.binding(mapped as never) !== undefined) {\n void setFocus(lesson.id).catch(reportError)\n ctx.sessions.open(mapped as never)\n return\n }\n setJumping(lesson.id)\n void setFocus(lesson.id).then(() => (async () => {\n // Minting a lesson session prompts directly (not through `send`), so\n // activation happens here too — same dormant-install guarantee.\n if (data?.active !== true) await activate(true)\n const area = await fetch('/lookatstudy/api/study-workspace')\n if (!area.ok) throw new Error(`study area unavailable (HTTP ${area.status})`)\n const { path } = await area.json() as { path: string }\n const workspace = await ctx.workspaces.create({ path })\n // connectWorkspace mints a fresh blank session once the workspace's\n // previous blank is used — each lesson thereby gets its own thread.\n const sessionId = await ctx.workspaces.connectWorkspace(workspace.workspaceId)\n const actx = ctx.sessions.scope(sessionId)\n const face = actx === undefined ? undefined : ctx.sessions.sessionOf(actx)\n if (face === undefined) throw new Error('lesson session is not addressable yet')\n const result = await face.prompt([{ type: 'text', text: `学习「${lesson.title}」:用 study_lesson 打开这一课开始学习。` }], 'queue')\n if (!result.ok) throw new Error(`lesson prompt rejected: ${result.error.code}: ${result.error.message}`)\n await bindLessonSession(lesson.id, sessionId)\n ctx.sessions.open(sessionId)\n })()).then(\n () => { setJumping(null) },\n (err: unknown) => { reportError(err); setJumping(null) },\n )\n }\n const [selectedCourse, setSelectedCourse] = useState('')\n const [query, setQuery] = useState('')\n const [confirmDelete, setConfirmDelete] = useState(false)\n const [showImport, setShowImport] = useState(false)\n /** Per-section open overrides (courseId/sectionTitle → open?), on top of the frontier default. */\n const [secOpen, setSecOpen] = useState<Record<string, boolean>>({})\n const [error, setError] = useState<string | null>(null)\n const reportError = (err: unknown): void => { setError(err instanceof Error ? err.message : String(err)) }\n const body: ReactNode = data === null\n ? createElement('div', { className: 'lks-empty' }, '加载中…')\n : data.courses.length === 0\n ? createElement('div', { className: 'lks-empty' },\n '暂无课程', createElement('br'),\n createElement('button', {\n className: 'lks-btn primary',\n style: { margin: '10px 0' },\n onClick: () => { send('导入课程:用 study_import_github 抓取 https://github.com/microsoft/AI-For-Beginners') },\n }, createElement(IconDownloadOutline16, null), '导入示例课程'),\n createElement(ImportRow, { send }),\n )\n : (() => {\n const courseId = data.courses.some(c => c.courseId === selectedCourse)\n ? selectedCourse\n : data.courses[0]!.courseId\n const course = data.courses.find(c => c.courseId === courseId)!\n return createElement('div', null,\n createElement('div', { className: 'lks-rail-head' },\n data.courses.length > 1\n ? createElement('select', {\n className: 'lks-rail-select',\n value: courseId,\n onChange: (e: { target: { value: string } }) => { setSelectedCourse(e.target.value); setConfirmDelete(false) },\n }, ...data.courses.map(c => createElement('option', { key: c.courseId, value: c.courseId }, c.title)))\n : createElement('div', { className: 'lks-rail-title' }, course.title),\n createElement('button', {\n className: `lks-btn ${confirmDelete ? 'primary' : 'ghost'}`,\n title: confirmDelete ? '再点一次确认删除(含全部进度与笔记)' : '删除本课程',\n onClick: () => {\n if (!confirmDelete) { setConfirmDelete(true); return }\n setConfirmDelete(false)\n deleteCourse(courseId).then(() => { setSelectedCourse('') }, reportError)\n },\n }, confirmDelete ? '确认删除?' : '🗑'),\n ),\n createElement('div', { className: 'lks-rail-sub' }, `${course.mastered}/${course.total} 已掌握`),\n createElement('div', {\n className: `lks-masterybar${course.avgMasteryPct === 100 ? ' gold' : ''}`,\n title: course.avgMasteryPct === null ? '尚无掌握度数据' : `平均掌握度 ${course.avgMasteryPct}%`,\n }, createElement('i', { style: { width: `${course.avgMasteryPct ?? 0}%` } })),\n createElement('input', {\n className: 'lks-search',\n type: 'search',\n placeholder: '搜索课时…(多关键词空格分隔)',\n value: query,\n onChange: (e: { target: { value: string } }) => { setQuery(e.target.value) },\n onKeyDown: (e: ReactKeyboardEvent<HTMLInputElement>) => {\n if (e.key !== 'Enter' || query.trim() === '') return\n for (const section of course.sections) {\n const hit = section.lessons.find(l => l.kind !== 'exam' && l.status !== 'locked' && titleMatches(l.title, query))\n if (hit !== undefined) {\n void setFocus(hit.id).catch(reportError)\n return\n }\n }\n },\n }),\n course.sections.some(s => s.lessons.some(l => l.focus))\n ? createElement('button', {\n className: 'lks-btn ghost',\n style: { margin: '0 0 6px', padding: '3px 8px', fontSize: '13px' },\n title: '在课程树中定位当前焦点课时(自动展开所在章节)',\n onClick: () => {\n const focusSection = course.sections.find(s => s.lessons.some(l => l.focus))\n if (focusSection === undefined) return\n setSecOpen(m => ({ ...m, [`${courseId}/${focusSection.title}`]: true }))\n // One frame past the React commit, so the freshly-expanded node exists to scroll to.\n window.setTimeout(() => {\n document.querySelector('.lks-col-rail .lks-node.focus')?.scrollIntoView({ block: 'center', behavior: 'smooth' })\n }, 60)\n },\n }, '📍 回到当前课时')\n : null,\n data.dueCount > 0\n ? createElement('div', { className: 'lks-duebox' },\n `🔁 待复习 ${data.dueCount}`,\n ...data.due.map(d => createElement('div', { key: d.lessonId, className: 'lks-due-item' },\n createElement('span', null, d.lessonTitle),\n d.overdueDays > 0 ? createElement('span', { className: 'lks-over' }, `超${d.overdueDays}天`) : null)),\n createElement('button', {\n className: 'lks-btn ghost',\n style: { marginTop: '6px' },\n onClick: () => { send('开始今天的复习,从最到期的课时开始') },\n }, '开始复习'),\n )\n : null,\n ...course.sections.flatMap(section => {\n const examAllowed = examOpen(section.lessons)\n const lessons = section.lessons.filter(l => query.trim() === '' || titleMatches(l.title, query) || l.focus)\n if (lessons.length === 0) return []\n const secKey = `${courseId}/${section.title}`\n // Active search overrides collapse (results must be visible);\n // otherwise the user's toggle wins over the frontier default.\n const open = query.trim() !== '' || (secOpen[secKey] ?? sectionDefaultOpen(section))\n return [\n createElement('button', {\n key: section.title,\n type: 'button',\n className: 'lks-sechead',\n 'aria-expanded': String(open),\n title: open ? '折叠本章节' : `展开本章节(${section.lessons.length} 课时)`,\n onClick: () => { setSecOpen(m => ({ ...m, [secKey]: !open })) },\n },\n createElement('span', { className: 'lks-sec-num' }, String(section.index + 1)),\n createElement('span', { className: 'lks-sechead-t' }, section.title),\n open ? null : createElement('span', { className: 'lks-sechead-n' }, `${section.lessons.length} 课`),\n createElement('span', { className: 'lks-sechead-c' }, open ? '▾' : '▸')),\n ...(open ? lessons.map(lesson => {\n const locked = lesson.status === 'locked' || (lesson.kind === 'exam' && !examAllowed)\n // aria-disabled (not the disabled attribute): the row stays\n // focusable so its title can explain WHY it is locked.\n return createElement('button', {\n key: lesson.id,\n type: 'button',\n className: `lks-node${lesson.focus ? ' focus' : ''}`,\n 'aria-disabled': locked || undefined,\n title: jumping === lesson.id ? '正在打开课时会话…' : statusTitle(lesson.kind, locked ? 'locked' : lesson.status),\n onClick: () => {\n if (locked) return\n if (lesson.kind === 'exam') {\n void setFocus(lesson.id).catch(reportError)\n send(`开始「${section.title}」的章节测验:按本节课时出题,答完逐题判分`)\n return\n }\n openLessonThread(lesson)\n },\n },\n createElement('span', { className: 'lks-g' }, jumping === lesson.id ? '⏳' : glyph(lesson.kind, locked ? 'locked' : lesson.status)),\n createElement('span', { className: 'lks-t' }, lesson.title),\n lesson.due ? createElement('span', { className: 'lks-tag due', title: '这课时的复习今天到期(SM-2)' }, '🔁') : null,\n lesson.weakConcepts > 0 ? createElement('span', { className: 'lks-tag weak', title: `${lesson.weakConcepts} 个薄弱知识点,测验会优先考察` }, `⚡${lesson.weakConcepts}`) : null,\n lesson.frictionCount > 0 ? createElement('span', { className: 'lks-tag fric', title: `${lesson.frictionCount} 次卡点记录(你说\"不懂\"时导师记下的)` }, `😣${lesson.frictionCount}`) : null,\n lesson.masteryPct !== null\n ? createElement('span', { className: 'lks-bar', title: `课时掌握度 ${lesson.masteryPct}%(取最薄弱知识点)` }, createElement('i', { style: { width: `${lesson.masteryPct}%` } }))\n : null,\n lesson.masteryPct !== null ? createElement('span', { className: 'lks-pct', title: '课时掌握度 = 最薄弱知识点的掌握度' }, `${lesson.masteryPct}%`) : null,\n )\n }) : []),\n ]\n }),\n createElement('button', {\n className: 'lks-btn ghost',\n style: { marginTop: '10px' },\n onClick: () => { setShowImport(!showImport) },\n }, showImport ? '收起导入' : '+ 导入课程'),\n showImport ? createElement(ImportRow, { send }) : null,\n )\n })()\n return createElement('div', { className: 'lks-col lks-col-rail' },\n createElement('div', { className: 'lks-colhead' }, '课程'),\n body,\n createElement(ActionError, { error }),\n )\n}\n\n/** One transcript row's element; `interactive` adds the quiz-answer buttons to an assistant row. */\nfunction chatRowElement(row: ChatRow, interactive?: { send: StudySend }): ReactNode {\n if (row.role === 'user') {\n return createElement('div', { key: row.key, className: 'lks-msg user' }, row.text)\n }\n if (row.role === 'tool') {\n const chip = row.call === undefined || row.call === null ? null : toolChipLabel(row.call.name, row.call.argsRaw)\n if (chip !== null) {\n return createElement('div', { key: row.key, className: `lks-msg tool ${chip.tone}` }, chip.label)\n }\n return createElement('div', { key: row.key, className: 'lks-msg tool' }, `🔧 ${row.text}`)\n }\n if (row.role === 'error') {\n return createElement('div', { key: row.key, className: 'lks-msg error' }, `⚠ ${row.text}`)\n }\n if (row.role === 'thinking') {\n return createElement('div', { key: row.key, className: 'lks-msg thinking', title: '导师正在推理,回复马上就来' },\n createElement(IconLoadingOutline16, { size: 14, className: 'lks-spin' }),\n row.text)\n }\n const cls = row.role === 'streaming' ? 'lks-msg assistant lks-prose streaming' : 'lks-msg assistant lks-prose'\n const body = createElement('div', {\n key: row.key,\n className: cls,\n dangerouslySetInnerHTML: { __html: renderMarkdown(row.text) },\n })\n const options = interactive === undefined || row.role === 'streaming' ? [] : quizOptions(row.text)\n if (options.length < 2) return body\n return createElement('div', { key: row.key, className: 'lks-turn' },\n body,\n createElement('div', { className: 'lks-quiz', title: '点击选项作答,也可以直接打字回答' },\n ...options.map(opt => createElement('button', {\n key: opt.letter,\n className: 'lks-opt',\n onClick: () => { interactive.send(`选 ${opt.letter}:${opt.text}`) },\n },\n createElement('span', { className: 'lks-optletter' }, opt.letter),\n createElement('span', null, opt.text))),\n ))\n}\n\n/** Middle column: the tutor — transcript, proposal banner, pills, starters. Typing happens in the native composer below the tab. */\nfunction TutorColumn({ data, setMode, send, snapshot }: {\n data: StudyData\n setMode: (mode: 'direct' | 'guide' | 'practice') => Promise<void>\n send: StudySend\n snapshot: ConversationSnapshot\n}): ReactNode {\n const [error, setError] = useState<string | null>(null)\n const rows = transcriptRows(snapshot.nodes, snapshot.partial)\n // Quiz buttons only on the last settled assistant reply: older questions are\n // already answered, and the streaming partial may cut an option mid-line.\n const lastAssistant = rows.reduce((acc, row, i) => row.role === 'assistant' ? i : acc, -1)\n const scrollRef = useRef<HTMLDivElement | null>(null)\n useEffect(() => {\n const el = scrollRef.current\n if (el !== null) el.scrollTop = el.scrollHeight\n }, [rows.length])\n const proposal = data?.pendingProposals[0] ?? null\n const lesson = data?.lesson ?? null\n /** Mode switches are the only fallible write left here (host route); surface failures inline. */\n const fire = (action: Promise<void>): void => {\n action.then(() => { setError(null) }, (err: unknown) => { setError(err instanceof Error ? err.message : String(err)) })\n }\n return createElement('div', { className: 'lks-col lks-col-tutor' },\n createElement('div', { className: 'lks-colhead' }, '导师'),\n createElement('div', { className: 'lks-transcript', ref: scrollRef },\n rows.length === 0\n ? createElement('div', { className: 'lks-empty' }, '对话会出现在这里', createElement('br'), '在下方输入框和导师说话')\n : rows.map((row, i) => chatRowElement(row, i === lastAssistant ? { send } : undefined)),\n ),\n proposal !== null\n ? createElement('div', { className: 'lks-banner' },\n createElement('span', null, '🎓'),\n createElement('span', { className: 'lks-why' }, `导师提议你已掌握「${proposal.lessonTitle}」:${proposal.rationale}`),\n createElement('button', {\n className: 'lks-btn primary',\n onClick: () => { send(`接受提案 ${proposal.id} —— 确认标记这课为已掌握`) },\n }, '接受'),\n createElement('button', {\n className: 'lks-btn ghost',\n onClick: () => { send(`拒绝提案 ${proposal.id} —— 我想再练练`) },\n }, '再练练'),\n )\n : null,\n createElement('div', { className: 'lks-pills', style: { height: 'auto', padding: '4px 0' } },\n ...MODES.map(mode => createElement('button', {\n key: mode.id,\n className: `lks-pill${data?.mode === mode.id ? ' on' : ''}`,\n title: mode.hint,\n onClick: () => { fire(setMode(mode.id)) },\n }, mode.label)),\n ),\n lesson !== null && lesson.starters.length > 0\n ? createElement('div', { className: 'lks-dock', style: { padding: '0 0 6px' } },\n ...lesson.starters.map(s => createElement('button', {\n key: s.label,\n className: 'lks-starter',\n title: s.message,\n onClick: () => { send(s.message) },\n }, s.label)),\n )\n : null,\n createElement(ActionError, { error }),\n )\n}\n\n/** Right column: the blackboard — focus-lesson 讲解 plus the Cornell 笔记. */\nfunction BlackboardColumn({ data }: { data: StudyData }): ReactNode {\n const lesson = data?.lesson ?? null\n const body: ReactNode = lesson === null\n ? createElement('div', { className: 'lks-empty' }, '黑板还空着', createElement('br'), '在左侧课程树选择一课')\n : createElement('div', null,\n createElement('div', { className: 'lks-lessonhead' },\n createElement('h2', null, lesson.title),\n createElement('div', { className: 'lks-meta' },\n `${lesson.courseTitle} · ${lesson.status}`\n + (lesson.masteryPct === null ? '' : ` · 掌握度 ${lesson.masteryPct}%`)\n + ` · ${lesson.strategy}`),\n lesson.concepts.length > 0\n ? createElement('div', { className: 'lks-chips' },\n ...lesson.concepts.map(c => createElement('span', {\n key: c.title,\n className: `lks-chip${c.weak ? ' weak' : ''}`,\n }, `${c.title} ${c.masteryPct}%${c.weak ? ' ⚡' : ''}`)))\n : null,\n ),\n createElement('div', { className: 'lks-prose', dangerouslySetInnerHTML: { __html: lesson.html } }),\n createElement('div', { className: 'lks-bb-notes' },\n createElement('div', { className: 'lks-sec' }, '笔记'),\n lesson.notes.length === 0\n ? createElement('div', { className: 'lks-empty', style: { padding: '16px 0' } }, '这一课还没有笔记')\n : ZONES.filter(([zone]) => lesson.notes.some(n => n.zone === zone)).map(([zone, label]) =>\n createElement('div', { key: zone, className: 'lks-zone' },\n createElement('h4', null, label),\n ...lesson.notes.filter(n => n.zone === zone).map(n => createElement('div', { key: n.id, className: 'lks-note' },\n createElement('span', { className: 'lks-note-src' }, n.source),\n createElement('div', { className: 'lks-note-title' }, n.title),\n createElement('div', { className: 'lks-note-text' }, n.text),\n n.quote !== null ? createElement('div', { className: 'lks-note-q' }, `“${n.quote}”`) : null,\n )),\n )),\n ),\n )\n return createElement('div', { className: 'lks-col lks-col-bb' },\n createElement('div', { className: 'lks-colhead' }, '黑板'),\n body,\n )\n}\n","/**\n * The one-click study starter: a pill button in the blank-session (hero)\n * composer tool row. One click adopts the plugin's dedicated study workspace\n * (created by the host beside the state file), opens a session in it, sends\n * the kickoff prompt through the session API, and makes that session current\n * — the learner lands in the chat with the tutor already responding, one\n * click away from the 学习 tab (view switching is ui-conversation-private,\n * not reachable from an additive plugin).\n * @module dsh-plugin-lookatstudy/client/starter\n */\n\nimport { createElement, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport { IconLoadingOutline16, IconThinkOutline16 } from './icons.tsx'\nimport { useStudy } from './data.ts'\nimport type { InputZone } from '@deepseek-ai/dsh-client-ui-conversation/client'\nimport type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'\n\n/** The kickoff prompt: starts the tutoring loop and points at the study tab. */\nconst KICKOFF = '开始学习:查看我的学习状态并打开当前焦点课时。如果我还没有课程,推荐我导入示例课程(AI-For-Beginners)并说明怎么开始。回复最后请提醒我点上方「学习」页签进入学习界面。'\n\n/**\n * Build the hero starter button bound to the framework services.\n * @param ctx - client root context (workspaces + sessions injected).\n * @returns the component for `conversation.input.left`.\n */\nexport function studyStartButton(ctx: ClientContext): (props: InputZone) => ReactNode {\n return function StudyStartButton({ session }: InputZone): ReactNode {\n if (session.blank !== true) return null\n return createElement(Inner, { key: 'inner', ctx })\n }\n}\n\n/** Button body in its own component so hook order stays constant. */\nfunction Inner({ ctx }: { ctx: ClientContext }): ReactNode {\n const { data, activate } = useStudy()\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState<string | null>(null)\n const start = (): void => {\n if (busy) return\n setBusy(true)\n setError(null)\n void (async () => {\n try {\n // Dormant installs activate first: the host registers the study tools\n // before this POST resolves, so the kickoff prompt below meets a model\n // that can already call them.\n if (data?.active !== true) await activate(true)\n const area = await fetch('/lookatstudy/api/study-workspace')\n if (!area.ok) throw new Error(`study area unavailable (HTTP ${area.status})`)\n const { path } = await area.json() as { path: string }\n const workspace = await ctx.workspaces.create({ path })\n const sessionId = await ctx.workspaces.connectWorkspace(workspace.workspaceId)\n const actx = ctx.sessions.scope(sessionId)\n const face = actx === undefined ? undefined : ctx.sessions.sessionOf(actx)\n if (face === undefined) throw new Error('study session is not addressable yet')\n const result = await face.prompt([{ type: 'text', text: KICKOFF }], 'queue')\n if (!result.ok) throw new Error(`kickoff prompt rejected: ${result.error.code}: ${result.error.message}`)\n ctx.sessions.open(sessionId)\n } catch (err) {\n setError(err instanceof Error ? err.message : String(err))\n setBusy(false)\n }\n })()\n }\n return createElement('span', { className: 'lks-root', style: { display: 'inline-flex', alignItems: 'center', gap: '8px' } },\n createElement('button', {\n className: 'lks-pill on',\n title: '一键准备学习:建立学习工作区、开启会话并让导师就位',\n disabled: busy,\n onClick: start,\n },\n busy\n ? createElement(IconLoadingOutline16, { className: 'lks-spin' })\n : createElement(IconThinkOutline16, null),\n busy ? '正在准备学习区…' : data?.active === true ? '进入学习' : '开始学习'),\n error !== null ? createElement('span', { className: 'lks-propcard-err', style: { marginTop: 0 } }, error) : null,\n )\n}\n","/**\n * Browser half of dsh-plugin-lookatstudy (`dsh.client`): ONE conversation\n * view tab — 「学习」— carrying the whole plugin. Inside the tab it is a\n * simplified LookatStudy in three columns (课程 | 老师 | 黑板); nothing\n * outside the tab modifies dsh chrome. Live study state comes from the shared\n * poll store over the host plugin's `/lookatstudy/api/*` routes; the tutor\n * column additionally reads the session snapshot through the framework\n * standard kit and sends messages via the same reverse channel as the\n * standalone workbench page.\n * @module dsh-plugin-lookatstudy/client\n */\n\nimport type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'\nimport { ensureStudyStyles } from './styles.ts'\nimport { studyView } from './views.tsx'\nimport { studyStartButton } from './starter.tsx'\n\nexport { studyView, transcriptRows } from './views.tsx'\nexport type { ChatRow } from './views.tsx'\n\nexport const inject = ['slots', 'workspaces', 'sessions']\n\n/**\n * Register the study surfaces: styles inject once; the single\n * `conversation.view` tab carries the whole plugin; the hero starter button\n * (`conversation.input.left`, blank sessions only) automates the onboarding —\n * study workspace + session + kickoff prompt in one click.\n * @param ctx - client root context.\n */\nexport function apply(ctx: ClientContext): void {\n ensureStudyStyles()\n ctx.slots.inject('conversation.view', () => ctx.slots.register(\n { name: 'conversation.view', id: 'lookatstudy-study', order: 15, label: '学习' },\n studyView(ctx),\n ))\n ctx.slots.inject('conversation.input.left', () => ctx.slots.register(\n { name: 'conversation.input.left', id: 'lookatstudy-start', order: 10 },\n studyStartButton(ctx),\n ))\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-lookatstudy",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "packageManager": "pnpm@11.7.0",
5
5
  "description": "Turn any markdown, local folder, or GitHub learning repo into a guided course inside DeepSeek Harness: gated skill-tree progression, BKT mastery tracking, SM-2 spaced repetition.",
6
6
  "type": "module",
@@ -14,7 +14,8 @@
14
14
  "scripts": {
15
15
  "build": "tsdown --config tsdown.config.ts",
16
16
  "test": "node --import tsx --test tests/*.test.ts",
17
- "verify": "node scripts/verify.mjs"
17
+ "verify": "node scripts/verify.mjs",
18
+ "judge": "node scripts/livetest-judge.mjs"
18
19
  },
19
20
  "keywords": [
20
21
  "dsh-plugin",