dsh-plugin-lookatstudy 0.12.2 → 0.12.4

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
@@ -298,10 +298,10 @@ window.__ModuleLoader__.load({
298
298
  * ic_ds_* glyphs vendored from @deepseek-ai/dsh-client-ui-primitives
299
299
  * (src/icons/index.tsx, same figma source as the host's icon set) so the
300
300
  * plugin's client bundle stays self-contained per the dsh client-bundle
301
- * contract — only react and dsh-client-runtime/client are runtime-external;
302
- * importing the host package root would drag its CSS modules into the
303
- * inlined bundle. Re-sync these three paths if the host icon set redesigns
304
- * them; scripts/verify.mjs pins the path data.
301
+ * contract — only react is runtime-external; importing the host package
302
+ * root would drag its CSS modules into the inlined bundle. Re-sync these
303
+ * three paths if the host icon set redesigns them; scripts/verify.mjs
304
+ * pins the path data.
305
305
  *
306
306
  * 2026-09-01 critique round: seven more ic_ds glyphs vendored (play/plus/
307
307
  * trash/refresh/globe/goal/warning) plus plugin-original learning-domain
@@ -2045,16 +2045,19 @@ window.__ModuleLoader__.load({
2045
2045
  return runs.at(-1) ?? [];
2046
2046
  }
2047
2047
  function textOf(blocks) {
2048
- return blocks.filter((b) => b.type === "text").map((b) => b.text ?? "").join("\n\n").trim();
2048
+ return (blocks ?? []).filter((b) => b.type === "text").map((b) => b.text ?? "").join("\n\n").trim();
2049
2049
  }
2050
2050
  function assistantText(blocks) {
2051
- return blocks.filter((b) => b.kind === "text").map((b) => b.text).join("\n\n").trim();
2051
+ return (blocks ?? []).filter((b) => b.kind === "text").map((b) => b.text ?? "").join("\n\n").trim();
2052
2052
  }
2053
2053
  /**
2054
2054
  * Fold conversation nodes (plus the streaming partial) into render rows.
2055
2055
  * Tool results are skipped: the host conversation renders the rich
2056
2056
  * `tool.call.toolview` cards, and the tutor column stays a pure persona
2057
2057
  * surface (a transcript mirror there grew a second, diverging record).
2058
+ * Both inputs tolerate undefined: newer hosts (DSH v0.1.2-alpha.4) mount
2059
+ * the conversation view before session data exists, and the fold must
2060
+ * degrade to an empty transcript instead of blanking the whole tab.
2058
2061
  * @param nodes - finalized conversation nodes from the snapshot.
2059
2062
  * @param partial - the in-flight assistant partial, or null.
2060
2063
  * @param t - translator (defaults to the active one, per call).
@@ -2062,7 +2065,7 @@ window.__ModuleLoader__.load({
2062
2065
  */
2063
2066
  function transcriptRows(nodes, partial, t = tr) {
2064
2067
  const rows = [];
2065
- for (const node of nodes) switch (node.kind) {
2068
+ for (const node of nodes ?? []) switch (node.kind) {
2066
2069
  case "user":
2067
2070
  case "steering": {
2068
2071
  const text = textOf(node.content);
@@ -2085,10 +2088,10 @@ window.__ModuleLoader__.load({
2085
2088
  case "turn-error": rows.push({
2086
2089
  key: `e${node.seq}`,
2087
2090
  role: "error",
2088
- text: node.message
2091
+ text: node.message ?? ""
2089
2092
  });
2090
2093
  }
2091
- if (partial !== null) {
2094
+ if (partial != null) {
2092
2095
  const text = assistantText(partial.blocks);
2093
2096
  if (text !== "") rows.push({
2094
2097
  key: "streaming",
@@ -2108,6 +2111,17 @@ window.__ModuleLoader__.load({
2108
2111
  if (error === null) return null;
2109
2112
  return (0, react.createElement)("div", { className: "lks-propcard-err" }, error);
2110
2113
  }
2114
+ /**
2115
+ * Resolve the transcript across host generations. 0.1.3+ hosts expose the
2116
+ * node array as the Chat target's `legacy` slice; pre-0.1.3 hosts hand the
2117
+ * whole conversation snapshot (nodes + partial) through `useSession`, whose
2118
+ * 0.1.3+ successor returns the session MACHINE (no node array). The array
2119
+ * check tells those two `useSession` shapes apart. Pure.
2120
+ */
2121
+ function pickTranscript(chatLegacy, sessionSnapshot) {
2122
+ if (chatLegacy !== void 0) return chatLegacy;
2123
+ return sessionSnapshot !== void 0 && Array.isArray(sessionSnapshot.nodes) ? sessionSnapshot : void 0;
2124
+ }
2111
2125
  /** sessionStorage key keeping the narrow-mode pane across view remounts (tab switches) and reloads. */
2112
2126
  const PANE_KEY = "dsh-plugin-lookatstudy:pane";
2113
2127
  /** Narrow the stored value back to a pane id; anything else (or nothing stored) falls back to 导师. Pure, unit-testable. */
@@ -2134,9 +2148,12 @@ window.__ModuleLoader__.load({
2134
2148
  };
2135
2149
  }
2136
2150
  /** Tab body: the factory-bound ctx carries workspaces/sessions for the per-lesson session jumps. */
2137
- function StudyTab({ useSession, inputActions, ctx }) {
2151
+ function StudyTab({ inputActions, ctx, ...standard }) {
2138
2152
  const { data, activate, setMode, setFocus, searchLessons, deleteCourse, bindLessonSession } = useStudy();
2139
- const snapshot = useSession((s) => s);
2153
+ const chatLegacy = standard.useChat?.((s) => s.legacy);
2154
+ const sessionSnapshot = standard.useSession?.((s) => s);
2155
+ const snapshot = pickTranscript(chatLegacy, sessionSnapshot);
2156
+ const currentSessionId = standard.sessionId ?? snapshot?.sessionId ?? "";
2140
2157
  const [pane, setPane] = (0, react.useState)(storedPane);
2141
2158
  const rootRef = (0, react.useRef)(null);
2142
2159
  (0, react.useEffect)(() => {
@@ -2234,7 +2251,7 @@ window.__ModuleLoader__.load({
2234
2251
  bindLessonSession,
2235
2252
  send,
2236
2253
  ctx,
2237
- currentSessionId: snapshot.sessionId
2254
+ currentSessionId
2238
2255
  }), (0, react.createElement)(TutorColumn, {
2239
2256
  data,
2240
2257
  setMode,
@@ -2265,7 +2282,7 @@ window.__ModuleLoader__.load({
2265
2282
  if (!area.ok) throw new Error(`study area unavailable (HTTP ${area.status})`);
2266
2283
  const { path } = await area.json();
2267
2284
  const workspace = await ctx.workspaces.create({ path });
2268
- const sessionId = await ctx.workspaces.connectWorkspace(workspace.workspaceId);
2285
+ const sessionId = ctx.workspaces.connectWorkspace !== void 0 ? await ctx.workspaces.connectWorkspace(workspace.workspaceId) : await ctx.sessions.create({ workspaceId: workspace.workspaceId });
2269
2286
  const actx = ctx.sessions.scope(sessionId);
2270
2287
  const face = actx === void 0 ? void 0 : ctx.sessions.sessionOf(actx);
2271
2288
  if (face === void 0) throw new Error("lesson session is not addressable yet");
@@ -2517,7 +2534,7 @@ window.__ModuleLoader__.load({
2517
2534
  /** Middle column: the tutor — transcript, proposal banner, pills, starters. Typing happens in the native composer below the tab. */
2518
2535
  function TutorColumn({ data, setMode, send, snapshot }) {
2519
2536
  const [error, setError] = (0, react.useState)(null);
2520
- const rows = transcriptRows(snapshot.nodes, snapshot.partial);
2537
+ const rows = transcriptRows(snapshot?.nodes, snapshot?.partial ?? null);
2521
2538
  const lastAssistant = rows.reduce((acc, row, i) => row.role === "assistant" ? i : acc, -1);
2522
2539
  const scrollRef = (0, react.useRef)(null);
2523
2540
  (0, react.useEffect)(() => {
@@ -2666,8 +2683,9 @@ window.__ModuleLoader__.load({
2666
2683
  * @returns the component for `conversation.input.left`.
2667
2684
  */
2668
2685
  function studyStartButton(ctx) {
2669
- return function StudyStartButton({ session }) {
2670
- if (session.blank !== true) return null;
2686
+ return function StudyStartButton(props) {
2687
+ const machine = props.useSession?.((s) => s);
2688
+ if ((props.session?.blank ?? machine?.blank) !== true) return null;
2671
2689
  return (0, react.createElement)(Inner, {
2672
2690
  key: "inner",
2673
2691
  ctx
@@ -2690,7 +2708,7 @@ window.__ModuleLoader__.load({
2690
2708
  if (!area.ok) throw new Error(`study area unavailable (HTTP ${area.status})`);
2691
2709
  const { path } = await area.json();
2692
2710
  const workspace = await ctx.workspaces.create({ path });
2693
- const sessionId = await ctx.workspaces.connectWorkspace(workspace.workspaceId);
2711
+ const sessionId = ctx.workspaces.connectWorkspace !== void 0 ? await ctx.workspaces.connectWorkspace(workspace.workspaceId) : await ctx.sessions.create({ workspaceId: workspace.workspaceId });
2694
2712
  const actx = ctx.sessions.scope(sessionId);
2695
2713
  const face = actx === void 0 ? void 0 : ctx.sessions.sessionOf(actx);
2696
2714
  if (face === void 0) throw new Error("study session is not addressable yet");