@oh-story/dsh 0.1.1 → 0.1.2

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
@@ -24,10 +24,21 @@ __export(index_exports, {
24
24
  name: () => name
25
25
  });
26
26
  module.exports = __toCommonJS(index_exports);
27
- var import_react = require("react"), import_react_dom = require("react-dom");
27
+ var import_client = require("@deepseek-ai/dsh-client-runtime/client"), import_react = require("react"), import_react_dom = require("react-dom");
28
28
 
29
29
  // src/client/file-activity.ts
30
30
  var STORY_DIRECTORIES = /* @__PURE__ */ new Set(["\u6B63\u6587", "\u5927\u7EB2", "\u8BBE\u5B9A", "\u8FFD\u8E2A", "\u5BF9\u6807", "\u53C2\u8003\u8D44\u6599"]), DRAMA_DIRECTORIES = /* @__PURE__ */ new Set(["\u8F93\u5165", "\u9879\u76EE\u5F00\u53D1", "\u8BBE\u5B9A\u96C6", "\u5267\u96C6", "\u4EA4\u4ED8", "\u521B\u4F5C\u8005\u51B3\u7B56", "\u5BA1\u67E5"]), EDITABLE_EXTENSION = /\.(?:md|txt|json|jsonl)$/iu, MUTATING_CALLS = /* @__PURE__ */ new Set(["write", "edit", "str_replace_editor", "bash", "run_code", "oh_story_role"]);
31
+ function streamingAssistant(timeline) {
32
+ for (let turnNumber of timeline.turnOrder.toReversed()) {
33
+ let turn = timeline.turns.get(turnNumber);
34
+ if (turn !== void 0)
35
+ for (let step of turn.steps.toReversed()) {
36
+ let assistant = step.data.get("assistant-step");
37
+ if (assistant?.status === "running") return assistant;
38
+ }
39
+ }
40
+ return null;
41
+ }
31
42
  function decodeEscape(character) {
32
43
  switch (character) {
33
44
  case '"':
@@ -167,13 +178,14 @@ function visitRunning(blocks, visit) {
167
178
  for (let block of blocks)
168
179
  "kind" in block || visit(block), visitRunning(block.subCalls, visit);
169
180
  }
170
- function fileMutations(runningCalls, projectedCalls = []) {
181
+ function fileMutations(runningCalls, partial = null) {
171
182
  let values = [];
172
183
  visitRunning(runningCalls, (call) => {
173
184
  values.push(...mutationsFromRunning(call));
174
185
  });
175
- for (let call of projectedCalls) {
176
- let value = mutationFromArgs(call.name, call.callId, call.argsRaw, "streaming");
186
+ for (let block of partial?.blocks ?? []) {
187
+ if (block.kind !== "tool-call") continue;
188
+ let value = mutationFromArgs(block.name, block.callId, block.argsRaw, "streaming");
177
189
  value !== void 0 && !values.some((candidate) => candidate.callId === value.callId) && values.push(value);
178
190
  }
179
191
  return values;
@@ -184,6 +196,26 @@ function mutatingCallIds(runningCalls) {
184
196
  MUTATING_CALLS.has(call.name) && ids.add(call.callId);
185
197
  }), ids;
186
198
  }
199
+ function settledMutationSignals(block) {
200
+ let nested = block.subCalls.flatMap(settledMutationSignals);
201
+ if (!("kind" in block) || block.isError) return nested;
202
+ let diffs = block.resultView?.card === "diff" ? block.resultView.diffs : block.callView?.card === "diff" ? block.callView.diffs : void 0;
203
+ if (diffs !== void 0) return [
204
+ ...diffs.map((diff, index) => `${block.callId}:${String(index)}\0${diff.path}`),
205
+ ...nested
206
+ ];
207
+ if (block.call === null) return nested;
208
+ let mutation = mutationFromArgs(block.call.name, block.callId, block.call.argsRaw, "running");
209
+ return mutation?.path === void 0 ? nested : [`${block.callId}\0${mutation.path}`, ...nested];
210
+ }
211
+ function latestSettledMutation(chat) {
212
+ for (let key of chat.order.toReversed()) {
213
+ let node = chat.nodes.get(key);
214
+ if (node?.kind !== "tool-call") continue;
215
+ let root = node.data.root, signal = root === void 0 ? void 0 : settledMutationSignals(root).at(-1);
216
+ if (signal !== void 0) return signal;
217
+ }
218
+ }
187
219
  function creativeRelativePath(path, cwd) {
188
220
  if (path === void 0 || path === "") return;
189
221
  let normalized = path.replaceAll("\\", "/"), root = cwd?.replaceAll("\\", "/").replace(/\/$/u, ""), insideRoot = root !== void 0 && normalized.startsWith(`${root}/`);
@@ -494,16 +526,16 @@ function MarkdownPreview({ content, label }) {
494
526
 
495
527
  // inline-css:/home/runner/work/oh-story-dsh/oh-story-dsh/packages/dsh-plugin/src/client/plugin.css
496
528
  var plugin_default = `.oh-story-bridge-marker { display: none; }
497
- .oh-story-split-surface { display: contents; }
529
+ .oh-story-split-surface { display: contents; font-family: var(--dsw-font-family); }
498
530
 
499
531
  /* DSH documents every [data-slot] wrapper as a stable addressable styling
500
532
  seam. The official Session body and composer stay mounted; this only turns
501
533
  their scroll host into a three-column writing surface. */
502
534
  [data-conversation-scroll]:has(> [data-slot="conversation.session"] > .oh-story-split-surface) {
503
535
  --os-border: var(--dsw-alias-border-l2);
504
- --os-muted: var(--dsw-alias-label-tertiary);
536
+ --os-muted: var(--dsw-alias-label-secondary);
505
537
  display: grid;
506
- grid-template-columns: clamp(96px, 16%, 190px) minmax(180px, 1fr) minmax(252px, 38%);
538
+ grid-template-columns: clamp(184px, 16%, 200px) minmax(240px, 1fr) clamp(408px, 40%, 520px);
507
539
  grid-template-rows: minmax(0, 1fr);
508
540
  min-height: 0;
509
541
  overflow-x: hidden;
@@ -571,7 +603,10 @@ var plugin_default = `.oh-story-bridge-marker { display: none; }
571
603
  gap: 12px;
572
604
  }
573
605
 
574
- .oh-story-brand { min-height: 28px; padding: 0 5px 10px 7px; font-size: 14px; font-weight: 500; }
606
+ .oh-story-brand { min-height: 28px; gap: 8px; padding: 0 5px 8px 7px; font-size: 14px; font-weight: 500; white-space: nowrap; }
607
+ .oh-story-brand-cluster { display: flex; min-width: 0; align-items: center; gap: 6px; }
608
+ .oh-story-brand-cluster strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font: inherit; }
609
+ .oh-story-kind { flex: none; padding: 1px 6px; border-radius: 5px; color: var(--dsw-alias-label-secondary); background: var(--dsw-alias-button-ghost-active-fill); font-size: 11px; line-height: 16px; font-weight: 500; }
575
610
 
576
611
  .oh-story-brand button,
577
612
  .oh-story-save,
@@ -597,39 +632,26 @@ var plugin_default = `.oh-story-bridge-marker { display: none; }
597
632
  .oh-story-role button:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary); outline-offset: -2px; }
598
633
  .oh-story-save:disabled { opacity: .48; cursor: default; }
599
634
 
600
- .oh-story-mode-tabs {
601
- display: grid;
602
- grid-template-columns: 1fr 1fr;
603
- margin: 0 4px 10px;
604
- border-bottom: 1px solid var(--os-border);
605
- }
635
+ .oh-story-mode-tabs { display: flex; margin: 0 4px 8px; padding: 2px; border: 1px solid var(--os-border); border-radius: 7px; background: var(--dsw-alias-bg-layer-1); }
606
636
 
607
637
  .oh-story-mode-tabs button,
608
638
  .oh-story-editor-tabs button {
609
639
  position: relative;
610
- min-height: 30px;
640
+ min-height: 24px;
641
+ flex: 1;
611
642
  border: 0;
612
643
  color: var(--os-muted);
613
644
  background: transparent;
645
+ border-radius: 5px;
614
646
  cursor: pointer;
615
647
  font: inherit;
616
- font-size: 12px;
648
+ font-size: 11px;
617
649
  }
618
650
 
619
651
  .oh-story-mode-tabs button:hover,
620
652
  .oh-story-editor-tabs button:hover { color: var(--dsw-alias-label-primary); }
621
653
  .oh-story-mode-tabs button[aria-selected="true"],
622
- .oh-story-editor-tabs button[aria-selected="true"] { color: var(--dsw-alias-label-primary); font-weight: 500; }
623
- .oh-story-mode-tabs button[aria-selected="true"]::after {
624
- position: absolute;
625
- right: 14px;
626
- bottom: -1px;
627
- left: 14px;
628
- height: 2px;
629
- border-radius: 2px 2px 0 0;
630
- background: var(--dsw-alias-state-business-primary);
631
- content: "";
632
- }
654
+ .oh-story-editor-tabs button[aria-selected="true"] { color: var(--dsw-alias-label-primary); background: var(--dsw-alias-bg-base); box-shadow: 0 1px 2px rgb(0 0 0 / 8%); font-weight: 500; }
633
655
 
634
656
  .oh-story-file-group,
635
657
  .oh-story-file-folder { margin: 0; }
@@ -647,6 +669,7 @@ var plugin_default = `.oh-story-bridge-marker { display: none; }
647
669
  font-size: 11px;
648
670
  font-weight: 600;
649
671
  }
672
+ .oh-story-file-folder > summary { padding-left: min(calc(7px + var(--oh-story-indent, 0px)), 49px); }
650
673
  .oh-story-file-group > summary { margin-top: 3px; }
651
674
  .oh-story-file-group > summary::-webkit-details-marker,
652
675
  .oh-story-file-folder > summary::-webkit-details-marker { display: none; }
@@ -657,13 +680,13 @@ var plugin_default = `.oh-story-bridge-marker { display: none; }
657
680
  .oh-story-file-group > summary:hover,
658
681
  .oh-story-file-folder > summary:hover { color: var(--dsw-alias-label-primary); background: var(--dsw-alias-interactive-bg-hover); }
659
682
  .oh-story-file-group > summary span,
660
- .oh-story-file-folder > summary span { margin-left: auto; color: var(--dsw-alias-label-caption); font-weight: 400; }
683
+ .oh-story-file-folder > summary span { margin-left: auto; color: var(--dsw-alias-label-secondary); font-weight: 400; }
661
684
 
662
685
  .oh-story-tree nav button {
663
686
  display: block;
664
687
  width: 100%;
665
688
  overflow: hidden;
666
- padding: 6px 8px;
689
+ padding: 6px 8px 6px min(calc(14px + var(--oh-story-indent, 0px)), 56px);
667
690
  border: 0;
668
691
  border-radius: 6px;
669
692
  color: inherit;
@@ -686,13 +709,17 @@ var plugin_default = `.oh-story-bridge-marker { display: none; }
686
709
  border-bottom: 1px solid var(--os-border);
687
710
  }
688
711
 
689
- .oh-story-editor header span {
712
+ .oh-story-editor-path {
690
713
  overflow: hidden;
714
+ min-width: 0;
715
+ flex: 1;
691
716
  text-overflow: ellipsis;
692
717
  white-space: nowrap;
693
718
  font-size: 12px;
694
719
  color: var(--os-muted);
695
720
  }
721
+ .oh-story-editor-path > span { overflow: hidden; display: block; text-overflow: ellipsis; }
722
+ .oh-story-editor-path > strong { display: none; overflow: hidden; text-overflow: ellipsis; font: inherit; }
696
723
 
697
724
  .oh-story-editor-actions { display: flex; flex: none; align-items: center; gap: 8px; }
698
725
  .oh-story-editor-tabs {
@@ -703,7 +730,6 @@ var plugin_default = `.oh-story-bridge-marker { display: none; }
703
730
  background: var(--dsw-alias-bg-layer-1);
704
731
  }
705
732
  .oh-story-editor-tabs button { min-height: 24px; padding: 0 9px; border-radius: 5px; font-size: 11px; }
706
- .oh-story-editor-tabs button[aria-selected="true"] { background: var(--dsw-alias-bg-base); box-shadow: 0 1px 2px rgb(0 0 0 / 8%); }
707
733
  .oh-story-save { min-height: 28px; }
708
734
 
709
735
  .oh-story-editor textarea {
@@ -714,7 +740,7 @@ var plugin_default = `.oh-story-bridge-marker { display: none; }
714
740
  resize: none;
715
741
  border: 0;
716
742
  outline: 0;
717
- padding: clamp(20px, 4vw, 48px) clamp(22px, 6vw, 84px) 48px;
743
+ padding: clamp(20px, 6%, 48px) clamp(22px, 10%, 84px) 48px;
718
744
  color: var(--dsw-alias-label-primary);
719
745
  background: transparent;
720
746
  font: 16px/1.9 ui-serif, "Songti SC", "STSong", Georgia, serif;
@@ -730,7 +756,7 @@ var plugin_default = `.oh-story-bridge-marker { display: none; }
730
756
  min-height: 0;
731
757
  flex: 1;
732
758
  overflow: auto;
733
- padding: clamp(24px, 5vw, 56px) clamp(24px, 7vw, 92px) 64px;
759
+ padding: clamp(24px, 8%, 56px) clamp(24px, 10%, 92px) 64px;
734
760
  color: var(--dsw-alias-label-primary);
735
761
  font: 16px/1.85 ui-serif, "Songti SC", "STSong", Georgia, serif;
736
762
  overflow-wrap: anywhere;
@@ -742,7 +768,7 @@ var plugin_default = `.oh-story-bridge-marker { display: none; }
742
768
  .oh-story-markdown h3,
743
769
  .oh-story-markdown h4,
744
770
  .oh-story-markdown h5,
745
- .oh-story-markdown h6 { margin: 1.45em 0 .6em; font-family: ui-sans-serif, system-ui, "PingFang SC", sans-serif; line-height: 1.35; letter-spacing: -.01em; }
771
+ .oh-story-markdown h6 { margin: 1.45em 0 .6em; font-family: var(--dsw-font-family); line-height: 1.35; letter-spacing: -.01em; }
746
772
  .oh-story-markdown h1 { font-size: 1.75em; }
747
773
  .oh-story-markdown h2 { padding-bottom: .35em; border-bottom: 1px solid var(--os-border); font-size: 1.35em; }
748
774
  .oh-story-markdown h3 { font-size: 1.15em; }
@@ -787,7 +813,7 @@ var plugin_default = `.oh-story-bridge-marker { display: none; }
787
813
  .oh-story-jsonl-records { overflow: auto; padding: 14px 18px 36px; }
788
814
  .oh-story-jsonl details,
789
815
  .oh-story-jsonl-error { overflow: hidden; margin-bottom: 10px; border: 1px solid var(--os-border); border-radius: 10px; background: var(--dsw-alias-bg-layer-1); }
790
- .oh-story-jsonl summary { display: flex; min-height: 42px; align-items: center; gap: 9px; padding: 0 12px; cursor: pointer; list-style: none; font: 12px/1.4 ui-sans-serif, system-ui, "PingFang SC", sans-serif; }
816
+ .oh-story-jsonl summary { display: flex; min-height: 42px; align-items: center; gap: 9px; padding: 0 12px; cursor: pointer; list-style: none; font: 12px/1.4 var(--dsw-font-family); }
791
817
  .oh-story-jsonl summary::-webkit-details-marker { display: none; }
792
818
  .oh-story-jsonl summary::before { content: "\u203A"; color: var(--os-muted); font-size: 16px; transition: transform .12s ease; }
793
819
  .oh-story-jsonl details[open] summary::before { transform: rotate(90deg); }
@@ -843,49 +869,104 @@ var plugin_default = `.oh-story-bridge-marker { display: none; }
843
869
  .oh-story-role pre { max-height: 260px; overflow: auto; margin: 4px 0 4px 4px; padding: 12px 16px; border: 1px solid var(--dsw-alias-border-l1); border-radius: 12px; color: var(--dsw-alias-label-secondary); background: var(--dsw-alias-markdown-code-block); white-space: pre-wrap; font: var(--dsw-font-markdown-code-block-small); }
844
870
  .oh-story-role > button { align-self: flex-start; margin: 4px 0 2px 4px; font-size: 11px; }
845
871
 
846
- @media (max-width: 980px) {
847
- [data-conversation-scroll]:has(> [data-slot="conversation.session"] > .oh-story-split-surface) {
848
- grid-template-columns: 104px minmax(210px, 1fr) minmax(252px, 40%);
849
- }
850
- .oh-story-editor textarea { padding-right: 28px; padding-left: 28px; }
872
+ [data-conversation-scroll][data-oh-story-layout="medium"]:has(> [data-slot="conversation.session"] > .oh-story-split-surface) {
873
+ grid-template-columns: clamp(104px, 18%, 184px) minmax(200px, 1fr) clamp(300px, 45%, 408px);
851
874
  }
852
875
 
853
- @media (max-width: 700px) {
854
- [data-conversation-scroll]:has(> [data-slot="conversation.session"] > .oh-story-split-surface) {
855
- grid-template-columns: 72px minmax(0, 1fr) 236px;
856
- }
857
- .oh-story-tree { padding-inline: 5px; }
858
- .oh-story-brand { padding-inline: 3px; font-size: 11px; }
859
- .oh-story-brand button { width: 24px; }
860
- .oh-story-mode-tabs { margin-inline: 0; }
861
- .oh-story-mode-tabs button { font-size: 10px; }
862
- .oh-story-editor header { padding-inline: 7px; }
863
- .oh-story-editor header > span { display: none; }
864
- .oh-story-editor-tabs button { padding-inline: 6px; }
865
- .oh-story-jsonl summary > code,
866
- .oh-story-jsonl summary > em { display: none; }
876
+ [data-conversation-scroll][data-oh-story-layout="compact"]:has(> [data-slot="conversation.session"] > .oh-story-split-surface) {
877
+ grid-template-columns: clamp(80px, 16%, 104px) minmax(0, 1fr) clamp(228px, 48%, 300px);
867
878
  }
868
- `;
869
879
 
870
- // src/file-activity-projection-types.ts
871
- var FILE_ACTIVITY_PROJECTION_KEY = "ohStoryFileActivity";
880
+ [data-oh-story-layout="medium"] .oh-story-file-folder > summary { padding-left: min(calc(7px + var(--oh-story-indent, 0px)), 35px); }
881
+ [data-oh-story-layout="medium"] .oh-story-tree nav button { padding-left: min(calc(14px + var(--oh-story-indent, 0px)), 42px); }
882
+ [data-oh-story-layout="medium"] .oh-story-editor textarea { padding-right: 28px; padding-left: 28px; }
883
+
884
+ [data-oh-story-layout="compact"] .oh-story-tree { padding-inline: 4px; }
885
+ [data-oh-story-layout="compact"] .oh-story-brand { gap: 4px; padding-inline: 2px; font-size: 11px; }
886
+ [data-oh-story-layout="compact"] .oh-story-brand-cluster { gap: 4px; }
887
+ [data-oh-story-layout="compact"] .oh-story-brand-cluster strong > span { display: none; }
888
+ [data-oh-story-layout="compact"] .oh-story-kind { padding-inline: 4px; font-size: 11px; }
889
+ [data-oh-story-layout="compact"] .oh-story-brand button { width: 24px; height: 24px; }
890
+ [data-oh-story-layout="compact"] .oh-story-mode-tabs { margin-inline: 0; }
891
+ [data-oh-story-layout="compact"] .oh-story-mode-tabs button { padding-inline: 3px; font-size: 11px; }
892
+ [data-oh-story-layout="compact"] .oh-story-file-folder > summary { padding-left: min(calc(5px + var(--oh-story-indent, 0px)), 19px); }
893
+ [data-oh-story-layout="compact"] .oh-story-tree nav button { padding-right: 4px; padding-left: min(calc(7px + var(--oh-story-indent, 0px)), 21px); }
894
+ [data-oh-story-layout="compact"] .oh-story-editor header {
895
+ display: grid;
896
+ min-height: 68px;
897
+ grid-template-columns: minmax(0, 1fr);
898
+ grid-template-rows: 24px 36px;
899
+ align-content: center;
900
+ gap: 0;
901
+ padding: 4px 3px;
902
+ }
903
+ [data-oh-story-layout="compact"] .oh-story-editor-path > span { display: none; }
904
+ [data-oh-story-layout="compact"] .oh-story-editor-path > strong { display: block; font-size: 11px; line-height: 24px; }
905
+ [data-oh-story-layout="compact"] .oh-story-editor-actions { width: 100%; min-width: 0; justify-content: flex-end; gap: 4px; }
906
+ [data-oh-story-layout="compact"] .oh-story-editor-tabs { min-width: 0; flex: 1; }
907
+ [data-oh-story-layout="compact"] .oh-story-editor-tabs button { min-width: 0; padding-inline: 2px; white-space: nowrap; }
908
+ [data-oh-story-layout="compact"] .oh-story-save { flex: none; padding-inline: 4px; white-space: nowrap; font-size: 11px; }
909
+ [data-oh-story-layout="compact"] .oh-story-editor textarea { padding: 16px 12px 40px; }
910
+ [data-oh-story-layout="compact"] .oh-story-markdown { padding: 18px 12px 48px; }
911
+ [data-oh-story-layout="compact"] .oh-story-jsonl-summary { padding-inline: 10px; }
912
+ [data-oh-story-layout="compact"] .oh-story-jsonl-records { padding: 8px 7px 28px; }
913
+ [data-oh-story-layout="compact"] .oh-story-jsonl summary { gap: 6px; padding-inline: 7px; }
914
+ [data-oh-story-layout="compact"] .oh-story-jsonl summary > span { display: none; }
915
+ [data-oh-story-layout="compact"] .oh-story-jsonl summary > strong { min-width: 32px; }
916
+ [data-oh-story-layout="compact"] .oh-story-jsonl summary > code,
917
+ [data-oh-story-layout="compact"] .oh-story-jsonl summary > em { display: none; }
918
+ `;
872
919
 
873
920
  // src/client/index.tsx
874
- var import_jsx_runtime3 = require("react/jsx-runtime"), name = "oh-story", inject = ["sessions", "slots"], EMPTY_FILE_ACTIVITY_STORE = {
875
- subscribe: () => () => {
876
- },
877
- getSnapshot: () => {
878
- }
879
- }, WorkspaceRequestError = class extends Error {
921
+ var import_jsx_runtime3 = require("react/jsx-runtime"), name = "oh-story", inject = ["slots"];
922
+ function applyUpdate(current, update) {
923
+ return typeof update == "function" ? update(current) : update;
924
+ }
925
+ function createWorkbenchStore() {
926
+ return (0, import_client.defineStore)({
927
+ init: () => ({
928
+ buffers: {},
929
+ editorMode: "preview",
930
+ expanded: {},
931
+ selected: void 0,
932
+ workbench: "story"
933
+ }),
934
+ actions: {
935
+ setBuffers: (draft, update) => {
936
+ draft.buffers = applyUpdate(draft.buffers, update);
937
+ },
938
+ setEditorMode: (draft, update) => {
939
+ draft.editorMode = applyUpdate(draft.editorMode, update);
940
+ },
941
+ setExpanded: (draft, update) => {
942
+ draft.expanded = applyUpdate(draft.expanded, update);
943
+ },
944
+ setSelected: (draft, update) => {
945
+ draft.selected = applyUpdate(draft.selected, update);
946
+ },
947
+ setWorkbench: (draft, update) => {
948
+ draft.workbench = applyUpdate(draft.workbench, update);
949
+ }
950
+ }
951
+ });
952
+ }
953
+ var WorkspaceRequestError = class extends Error {
880
954
  constructor(status, message) {
881
955
  super(message);
882
956
  this.status = status;
883
957
  }
884
958
  status;
885
- }, workbenchMemory = /* @__PURE__ */ new Map(), GROUP_ORDER = {
959
+ }, GROUP_ORDER = {
886
960
  story: ["\u6B63\u6587", "\u5927\u7EB2", "\u8BBE\u5B9A", "\u8FFD\u8E2A", "\u5BF9\u6807", "\u53C2\u8003\u8D44\u6599"],
887
961
  drama: ["\u9879\u76EE", "\u8F93\u5165", "\u9879\u76EE\u5F00\u53D1", "\u8BBE\u5B9A\u96C6", "\u5267\u96C6", "\u5BA1\u67E5", "\u521B\u4F5C\u8005\u51B3\u7B56", "\u4EA4\u4ED8"]
888
- };
962
+ }, WORKBENCH_MODES = ["story", "drama"], EDITOR_MODES = ["preview", "source"];
963
+ function handleTabKey(event, values, current, select) {
964
+ let index;
965
+ if (event.key === "Home" ? index = 0 : event.key === "End" ? index = values.length - 1 : event.key === "ArrowRight" ? index = (values.indexOf(current) + 1) % values.length : event.key === "ArrowLeft" && (index = (values.indexOf(current) - 1 + values.length) % values.length), index === void 0) return;
966
+ event.preventDefault();
967
+ let value = values[index];
968
+ value !== void 0 && (select(value), event.currentTarget.parentElement?.querySelectorAll("[role='tab']")[index]?.focus());
969
+ }
889
970
  function groupForPath(path) {
890
971
  return path === "short-drama.json" ? "\u9879\u76EE" : path.split("/", 1)[0] ?? "\u5176\u4ED6";
891
972
  }
@@ -897,7 +978,6 @@ function preferredFile(files, mode) {
897
978
  }
898
979
  return matching[0]?.path;
899
980
  }
900
- var EMPTY_CALLS = [];
901
981
  function endpoint(path, sessionId, file) {
902
982
  let url = new URL(`/oh-story/${path}`, globalThis.location.origin);
903
983
  return url.searchParams.set("sessionId", sessionId), file !== void 0 && url.searchParams.set("path", file), url.toString();
@@ -921,7 +1001,9 @@ function FileTreeNodes({
921
1001
  "button",
922
1002
  {
923
1003
  type: "button",
924
- style: { paddingLeft: `${String(14 + depth * 14)}px` },
1004
+ style: { "--oh-story-indent": `${String(depth * 14)}px` },
1005
+ title: node.path,
1006
+ "aria-label": node.path,
925
1007
  "data-file-path": node.path,
926
1008
  "data-agent-target": node.path === activityPath || void 0,
927
1009
  "aria-current": node.path === selected ? "page" : void 0,
@@ -936,7 +1018,7 @@ function FileTreeNodes({
936
1018
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("details", { className: "oh-story-file-folder", open, onToggle: (event) => {
937
1019
  onToggle(node.path, event.currentTarget.open);
938
1020
  }, children: [
939
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("summary", { style: { paddingLeft: `${String(7 + depth * 14)}px` }, children: [
1021
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("summary", { style: { "--oh-story-indent": `${String(depth * 14)}px` }, title: node.path, children: [
940
1022
  node.name,
941
1023
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: node.fileCount })
942
1024
  ] }),
@@ -973,20 +1055,43 @@ function useWorkspace(sessionId) {
973
1055
  function CreativeWorkbench({
974
1056
  sessionId,
975
1057
  runningCalls,
976
- projectedCalls
1058
+ partial,
1059
+ settledMutation,
1060
+ useStore,
1061
+ actions
977
1062
  }) {
978
1063
  let { workspace, error, loading: workspaceLoading, reload } = useWorkspace(sessionId), activities = (0, import_react.useMemo)(
979
- () => fileMutations(runningCalls, projectedCalls),
980
- [projectedCalls, runningCalls]
1064
+ () => fileMutations(runningCalls, partial),
1065
+ [partial, runningCalls]
981
1066
  ), normalizedActivities = (0, import_react.useMemo)(() => activities.flatMap((activity2) => {
982
1067
  let path = creativeRelativePath(activity2.path, workspace?.cwd);
983
1068
  return path === void 0 ? [] : [{ activity: activity2, path }];
984
- }), [activities, workspace?.cwd]), primaryActivity = normalizedActivities.at(-1), activityPaths = (0, import_react.useMemo)(() => new Set(normalizedActivities.map((value) => value.path)), [normalizedActivities]), activity = primaryActivity?.activity, activityPath = primaryActivity?.path, remembered = (0, import_react.useMemo)(() => workbenchMemory.get(sessionId), [sessionId]), [workbench, setWorkbench] = (0, import_react.useState)(remembered?.workbench ?? "story"), initializedWorkbench = (0, import_react.useRef)(!1), [selected, setSelected] = (0, import_react.useState)(remembered?.selected), [buffers, setBuffers] = (0, import_react.useState)(remembered?.buffers ?? {}), buffersRef = (0, import_react.useRef)({}), [expanded, setExpanded] = (0, import_react.useState)(remembered?.expanded ?? {}), surfaceRef = (0, import_react.useRef)(null), navRef = (0, import_react.useRef)(null), activityBases = (0, import_react.useRef)(/* @__PURE__ */ new Map()), previousSignals = (0, import_react.useRef)(/* @__PURE__ */ new Set()), saveLocks = (0, import_react.useRef)(/* @__PURE__ */ new Set()), buffer = selected === void 0 ? void 0 : buffers[selected], dirty = buffer?.source === "human" && buffer.content !== buffer.saved, saving = buffer?.saving === !0, fileError = buffer?.error, conflict = buffer?.conflict, selectedLower = selected?.toLocaleLowerCase(), markdown = selectedLower?.endsWith(".md") === !0, jsonl = selectedLower?.endsWith(".jsonl") === !0, structured = jsonl || selectedLower?.endsWith(".json") === !0, previewable = markdown || jsonl, [editorMode, setEditorMode] = (0, import_react.useState)(remembered?.editorMode ?? "preview"), modeSelection = (0, import_react.useRef)(selected);
1069
+ }), [activities, workspace?.cwd]), primaryActivity = normalizedActivities.at(-1), activityPaths = (0, import_react.useMemo)(() => new Set(normalizedActivities.map((value) => value.path)), [normalizedActivities]), activity = primaryActivity?.activity, activityPath = primaryActivity?.path, workbench = useStore((memory) => memory.workbench), setWorkbench = actions.setWorkbench, initializedWorkbench = (0, import_react.useRef)(!1), selected = useStore((memory) => memory.selected), setSelected = actions.setSelected, buffers = useStore((memory) => memory.buffers), setBuffers = actions.setBuffers, buffersRef = (0, import_react.useRef)({}), expanded = useStore((memory) => memory.expanded), setExpanded = actions.setExpanded, surfaceRef = (0, import_react.useRef)(null), navRef = (0, import_react.useRef)(null), activityBases = (0, import_react.useRef)(/* @__PURE__ */ new Map()), previousSignals = (0, import_react.useRef)(/* @__PURE__ */ new Set()), previousSettledMutation = (0, import_react.useRef)(settledMutation), saveLocks = (0, import_react.useRef)(/* @__PURE__ */ new Set()), buffer = selected === void 0 ? void 0 : buffers[selected], dirty = buffer?.source === "human" && buffer.content !== buffer.saved, saving = buffer?.saving === !0, fileError = buffer?.error, conflict = buffer?.conflict, selectedLower = selected?.toLocaleLowerCase(), markdown = selectedLower?.endsWith(".md") === !0, jsonl = selectedLower?.endsWith(".jsonl") === !0, structured = jsonl || selectedLower?.endsWith(".json") === !0, previewable = markdown || jsonl, editorMode = useStore((memory) => memory.editorMode), setEditorMode = actions.setEditorMode, modeSelection = (0, import_react.useRef)(selected), textareaRef = (0, import_react.useRef)(null), editorPositions = (0, import_react.useRef)(/* @__PURE__ */ new Map()), editorReady = buffer !== void 0 && buffer.missing !== !0, availableModes = (0, import_react.useMemo)(() => {
1070
+ let value = /* @__PURE__ */ new Set();
1071
+ for (let file of workspace?.files ?? []) {
1072
+ let mode = workbenchModeForPath(file.path);
1073
+ mode !== void 0 && value.add(mode);
1074
+ }
1075
+ return WORKBENCH_MODES.filter((mode) => value.has(mode));
1076
+ }, [workspace?.files]), showModeTabs = workspace !== void 0 && availableModes.length !== 1, workspaceKind = availableModes.length === 1 ? availableModes[0] : void 0;
985
1077
  (0, import_react.useEffect)(() => {
986
1078
  buffersRef.current = buffers;
987
- }, [buffers]), (0, import_react.useEffect)(() => {
988
- workbenchMemory.set(sessionId, { buffers, editorMode, expanded, selected, workbench });
989
- }, [buffers, editorMode, expanded, selected, sessionId, workbench]), (0, import_react.useEffect)(() => {
1079
+ }, [buffers]);
1080
+ let rememberEditorPosition = (0, import_react.useCallback)(() => {
1081
+ let element = textareaRef.current;
1082
+ element === null || selected === void 0 || element.getAttribute("aria-label") !== selected || editorPositions.current.set(selected, {
1083
+ scrollTop: element.scrollTop,
1084
+ selectionStart: element.selectionStart,
1085
+ selectionEnd: element.selectionEnd
1086
+ });
1087
+ }, [selected]);
1088
+ (0, import_react.useLayoutEffect)(() => {
1089
+ if (editorMode !== "source" || selected === void 0 || !editorReady) return;
1090
+ let element = textareaRef.current, position = editorPositions.current.get(selected);
1091
+ if (element === null || position === void 0) return;
1092
+ let end = Math.min(position.selectionEnd, element.value.length);
1093
+ element.setSelectionRange(Math.min(position.selectionStart, end), end), element.scrollTop = position.scrollTop;
1094
+ }, [editorMode, editorReady, selected]), (0, import_react.useEffect)(() => {
990
1095
  let warn = (event) => {
991
1096
  Object.values(buffersRef.current).some((value) => value.source === "human" && value.content !== value.saved) && event.preventDefault();
992
1097
  };
@@ -994,8 +1099,7 @@ function CreativeWorkbench({
994
1099
  globalThis.removeEventListener("beforeunload", warn);
995
1100
  };
996
1101
  }, []);
997
- let revealPath = (0, import_react.useCallback)((path) => {
998
- setWorkbench(workbenchModeForPath(path) ?? "story"), setSelected(path);
1102
+ let expandPath = (0, import_react.useCallback)((path) => {
999
1103
  let segments = path.split("/"), ancestors = [groupForPath(path)];
1000
1104
  for (let index = 1; index < segments.length - 1; index += 1) ancestors.push(segments.slice(0, index + 1).join("/"));
1001
1105
  setExpanded((current) => {
@@ -1003,24 +1107,22 @@ function CreativeWorkbench({
1003
1107
  for (let ancestor of ancestors) next[ancestor] = !0;
1004
1108
  return next;
1005
1109
  });
1006
- }, []);
1110
+ }, []), revealPath = (0, import_react.useCallback)((path) => {
1111
+ rememberEditorPosition(), setWorkbench(workbenchModeForPath(path) ?? "story"), setSelected(path), expandPath(path);
1112
+ }, [expandPath, rememberEditorPosition]), followAgentPath = (0, import_react.useCallback)((path) => {
1113
+ expandPath(path);
1114
+ let current = selected === void 0 ? void 0 : buffersRef.current[selected];
1115
+ path !== selected && current?.source === "human" && current.content !== current.saved && surfaceRef.current?.ownerDocument.activeElement === textareaRef.current || revealPath(path);
1116
+ }, [expandPath, revealPath, selected]);
1007
1117
  (0, import_react.useEffect)(() => {
1008
- if (workspace === void 0 || initializedWorkbench.current) return;
1009
- let hasStory = workspace.files.some((file) => workbenchModeForPath(file.path) === "story");
1010
- workspace.shortDrama !== null && !hasStory && setWorkbench("drama"), initializedWorkbench.current = !0;
1011
- }, [workspace]), (0, import_react.useEffect)(() => {
1012
- activityPath !== void 0 && setEditorMode("source");
1013
- }, [activityPath]), (0, import_react.useEffect)(() => {
1118
+ workspace === void 0 || initializedWorkbench.current || (availableModes.length === 1 && setWorkbench(availableModes[0] ?? "story"), initializedWorkbench.current = !0);
1119
+ }, [availableModes, workspace]), (0, import_react.useEffect)(() => {
1120
+ activityPath !== void 0 && activityPath === selected && setEditorMode("source");
1121
+ }, [activityPath, selected]), (0, import_react.useEffect)(() => {
1014
1122
  modeSelection.current !== selected && (modeSelection.current = selected, setEditorMode(activityPath === selected ? "source" : previewable ? "preview" : "source"));
1015
1123
  }, [activityPath, previewable, selected]), (0, import_react.useEffect)(() => {
1016
- if (!workspaceLoading) {
1017
- if (activityPath !== void 0) {
1018
- revealPath(activityPath);
1019
- return;
1020
- }
1021
- selected !== void 0 && ((workspace?.files.some((file) => file.path === selected) ?? !1) || buffers[selected] !== void 0) && workbenchModeForPath(selected) === workbench || setSelected(workspace === void 0 ? void 0 : preferredFile(workspace.files, workbench));
1022
- }
1023
- }, [activityPath, buffers, revealPath, selected, workbench, workspace, workspaceLoading]), (0, import_react.useEffect)(() => {
1124
+ workspaceLoading || activityPath === void 0 && (selected !== void 0 && ((workspace?.files.some((file) => file.path === selected) ?? !1) || buffers[selected] !== void 0) && workbenchModeForPath(selected) === workbench || setSelected(workspace === void 0 ? void 0 : preferredFile(workspace.files, workbench)));
1125
+ }, [activityPath, buffers, selected, workbench, workspace, workspaceLoading]), (0, import_react.useEffect)(() => {
1024
1126
  if (workspace === void 0 || workspaceLoading) return;
1025
1127
  let paths = new Set(workspace.files.map((file) => file.path));
1026
1128
  setBuffers((current) => {
@@ -1068,8 +1170,8 @@ function CreativeWorkbench({
1068
1170
  };
1069
1171
  }, [activityPaths, selected, sessionId, workspace?.files]), (0, import_react.useEffect)(() => {
1070
1172
  if (normalizedActivities.length !== 0) {
1071
- for (let { path } of normalizedActivities) revealPath(path);
1072
- setBuffers((current) => {
1173
+ for (let { path } of normalizedActivities) expandPath(path);
1174
+ activityPath !== void 0 && followAgentPath(activityPath), setBuffers((current) => {
1073
1175
  let next = current;
1074
1176
  for (let { activity: currentActivity, path } of normalizedActivities) {
1075
1177
  let existing = next[path];
@@ -1099,7 +1201,7 @@ function CreativeWorkbench({
1099
1201
  return next;
1100
1202
  });
1101
1203
  }
1102
- }, [normalizedActivities, revealPath]), (0, import_react.useEffect)(() => {
1204
+ }, [activityPath, expandPath, followAgentPath, normalizedActivities]), (0, import_react.useEffect)(() => {
1103
1205
  let signals = new Set(mutatingCallIds(runningCalls));
1104
1206
  for (let { activity: currentActivity } of normalizedActivities) signals.add(currentActivity.callId.split(":", 1)[0] ?? currentActivity.callId);
1105
1207
  let settled = [...previousSignals.current].some((callId) => !signals.has(callId));
@@ -1107,6 +1209,11 @@ function CreativeWorkbench({
1107
1209
  signals.has(callId.split(":", 1)[0] ?? callId) || activityBases.current.delete(callId);
1108
1210
  previousSignals.current = signals, settled && reload();
1109
1211
  }, [normalizedActivities, reload, runningCalls]), (0, import_react.useEffect)(() => {
1212
+ if (settledMutation === void 0 || settledMutation === previousSettledMutation.current) return;
1213
+ previousSettledMutation.current = settledMutation;
1214
+ let path = creativeRelativePath(settledMutation.slice(settledMutation.indexOf("\0") + 1), workspace?.cwd);
1215
+ path !== void 0 && followAgentPath(path), reload();
1216
+ }, [followAgentPath, reload, settledMutation, workspace?.cwd]), (0, import_react.useEffect)(() => {
1110
1217
  if (selected !== void 0) {
1111
1218
  for (let button of navRef.current?.querySelectorAll("button[data-file-path]") ?? [])
1112
1219
  if (button.dataset.filePath === selected) {
@@ -1230,7 +1337,9 @@ function CreativeWorkbench({
1230
1337
  setWorkbench(next);
1231
1338
  let target = workspace === void 0 ? void 0 : preferredFile(workspace.files, next);
1232
1339
  target === void 0 ? setSelected(void 0) : revealPath(target);
1233
- }, selectedGroup = selected === void 0 ? void 0 : groupForPath(selected), toggleGroup = (key, open) => {
1340
+ }, selectEditorMode = (next) => {
1341
+ next === "preview" && rememberEditorPosition(), setEditorMode(next);
1342
+ }, selectedLabel = selected ?? `\u5728\u5F53\u524D DSH workspace \u4E2D\u9009\u62E9${workbench === "story" ? "\u5C0F\u8BF4" : "\u77ED\u5267"}\u6587\u4EF6`, selectedBasename = selected?.split("/").at(-1) ?? selectedLabel, selectedGroup = selected === void 0 ? void 0 : groupForPath(selected), toggleGroup = (key, open) => {
1234
1343
  setExpanded((current) => ({ ...current, [key]: open }));
1235
1344
  }, resolveConflict = (keepLocal) => {
1236
1345
  if (selected === void 0 || conflict?.theirs === void 0 || conflict.theirsVersion === void 0) return;
@@ -1247,17 +1356,32 @@ function CreativeWorkbench({
1247
1356
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("style", { children: plugin_default }),
1248
1357
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("aside", { className: "oh-story-tree", children: [
1249
1358
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "oh-story-brand", children: [
1250
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "\u2726 Oh Story" }),
1251
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", onClick: reload, title: "\u5237\u65B0", children: "\u21BB" })
1252
- ] }),
1253
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "oh-story-mode-tabs", role: "tablist", "aria-label": "\u521B\u4F5C\u5DE5\u4F5C\u53F0", children: [
1254
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", role: "tab", "aria-selected": workbench === "story", onClick: () => {
1255
- selectWorkbench("story");
1256
- }, children: "\u5C0F\u8BF4" }),
1257
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", role: "tab", "aria-selected": workbench === "drama", onClick: () => {
1258
- selectWorkbench("drama");
1259
- }, children: "\u77ED\u5267" })
1359
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "oh-story-brand-cluster", children: [
1360
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("strong", { children: [
1361
+ "\u2726 ",
1362
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "Oh Story" })
1363
+ ] }),
1364
+ workspaceKind !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "oh-story-kind", children: workspaceKind === "story" ? "\u5C0F\u8BF4" : "\u77ED\u5267" })
1365
+ ] }),
1366
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", onClick: reload, title: "\u5237\u65B0", "aria-label": "\u5237\u65B0\u9879\u76EE\u6587\u4EF6", children: "\u21BB" })
1260
1367
  ] }),
1368
+ showModeTabs && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "oh-story-mode-tabs", role: "tablist", "aria-label": "\u521B\u4F5C\u5DE5\u4F5C\u53F0", children: WORKBENCH_MODES.map((mode) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1369
+ "button",
1370
+ {
1371
+ type: "button",
1372
+ role: "tab",
1373
+ tabIndex: workbench === mode ? 0 : -1,
1374
+ "aria-selected": workbench === mode,
1375
+ onKeyDown: (event) => {
1376
+ handleTabKey(event, WORKBENCH_MODES, workbench, selectWorkbench);
1377
+ },
1378
+ onClick: () => {
1379
+ selectWorkbench(mode);
1380
+ },
1381
+ children: mode === "story" ? "\u5C0F\u8BF4" : "\u77ED\u5267"
1382
+ },
1383
+ mode
1384
+ )) }),
1261
1385
  error !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "oh-story-error", children: error }),
1262
1386
  workspace?.metadataErrors.map((message) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "oh-story-warning", children: message }, message)),
1263
1387
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("nav", { ref: navRef, "aria-label": workbench === "story" ? "\u5C0F\u8BF4\u9879\u76EE\u6587\u4EF6" : "\u77ED\u5267\u9879\u76EE\u6587\u4EF6", children: groups.map(([directory2, files]) => {
@@ -1286,16 +1410,28 @@ function CreativeWorkbench({
1286
1410
  ] }),
1287
1411
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("main", { className: "oh-story-editor", children: [
1288
1412
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("header", { children: [
1289
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { title: selected, children: selected ?? `\u5728\u5F53\u524D DSH workspace \u4E2D\u9009\u62E9${workbench === "story" ? "\u5C0F\u8BF4" : "\u77ED\u5267"}\u6587\u4EF6` }),
1413
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "oh-story-editor-path", title: selected, children: [
1414
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: selectedLabel }),
1415
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: selectedBasename })
1416
+ ] }),
1290
1417
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "oh-story-editor-actions", children: [
1291
- previewable && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "oh-story-editor-tabs", role: "tablist", "aria-label": markdown ? "Markdown \u67E5\u770B\u65B9\u5F0F" : "JSONL \u67E5\u770B\u65B9\u5F0F", children: [
1292
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", role: "tab", "aria-selected": editorMode === "preview", onClick: () => {
1293
- setEditorMode("preview");
1294
- }, children: "\u9884\u89C8" }),
1295
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", role: "tab", "aria-selected": editorMode === "source", onClick: () => {
1296
- setEditorMode("source");
1297
- }, children: "\u6E90\u7801" })
1298
- ] }),
1418
+ previewable && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "oh-story-editor-tabs", role: "tablist", "aria-label": markdown ? "Markdown \u67E5\u770B\u65B9\u5F0F" : "JSONL \u67E5\u770B\u65B9\u5F0F", children: EDITOR_MODES.map((mode) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1419
+ "button",
1420
+ {
1421
+ type: "button",
1422
+ role: "tab",
1423
+ tabIndex: editorMode === mode ? 0 : -1,
1424
+ "aria-selected": editorMode === mode,
1425
+ onKeyDown: (event) => {
1426
+ handleTabKey(event, EDITOR_MODES, editorMode, selectEditorMode);
1427
+ },
1428
+ onClick: () => {
1429
+ selectEditorMode(mode);
1430
+ },
1431
+ children: mode === "preview" ? "\u9884\u89C8" : "\u6E90\u7801"
1432
+ },
1433
+ mode
1434
+ )) }),
1299
1435
  (dirty || saving) && selected !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { className: "oh-story-save", type: "button", disabled: saving || buffer?.missing === !0, onClick: () => {
1300
1436
  savePath(selected);
1301
1437
  }, children: saving ? "\u4FDD\u5B58\u4E2D\u2026" : "\u4FDD\u5B58" })
@@ -1340,8 +1476,12 @@ function CreativeWorkbench({
1340
1476
  ] }) : previewable && editorMode === "preview" ? markdown ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MarkdownPreview, { content: buffer.content, label: selected }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(JsonlPreview, { content: buffer.content, label: selected }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1341
1477
  "textarea",
1342
1478
  {
1479
+ ref: textareaRef,
1343
1480
  value: buffer.content,
1344
1481
  "data-format": structured ? "structured" : "prose",
1482
+ onBlur: rememberEditorPosition,
1483
+ onScroll: rememberEditorPosition,
1484
+ onSelect: rememberEditorPosition,
1345
1485
  onChange: (event) => {
1346
1486
  let content = event.target.value;
1347
1487
  setBuffers((current) => ({
@@ -1363,12 +1503,8 @@ function CreativeWorkbench({
1363
1503
  ] })
1364
1504
  ] });
1365
1505
  }
1366
- function CreativeSplitBridge({ context }) {
1367
- let marker = (0, import_react.useRef)(null), [target, setTarget] = (0, import_react.useState)(), provideFace = context.sessions.currentProvideInfo, subscribeProvide = (0, import_react.useCallback)((listener) => provideFace.subscribe(listener), [provideFace]), getProvide = (0, import_react.useCallback)(() => provideFace.getSnapshot(), [provideFace]), provide = (0, import_react.useSyncExternalStore)(subscribeProvide, getProvide, getProvide), sessionId = provide.sessionId, source = provide.hooks.session, subscribe = (0, import_react.useCallback)((listener) => source?.subscribe(listener) ?? (() => {
1368
- }), [source]), getSnapshot = (0, import_react.useCallback)(() => source?.getSnapshot(), [source]), snapshot = (0, import_react.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot), projectionSource = (0, import_react.useMemo)(
1369
- () => provide.projections?.faceOf(FILE_ACTIVITY_PROJECTION_KEY) ?? EMPTY_FILE_ACTIVITY_STORE,
1370
- [provide.projections]
1371
- ), subscribeProjection = (0, import_react.useCallback)((listener) => projectionSource.subscribe(listener), [projectionSource]), getProjection = (0, import_react.useCallback)(() => projectionSource.getSnapshot(), [projectionSource]), projection = (0, import_react.useSyncExternalStore)(subscribeProjection, getProjection, getProjection);
1506
+ function CreativeSplitBridge({ sessionId, useSession, useStore, actions }) {
1507
+ let marker = (0, import_react.useRef)(null), [target, setTarget] = (0, import_react.useState)(), runningCalls = useSession((snapshot) => snapshot.runningCalls), partial = useSession((snapshot) => streamingAssistant(snapshot.chat.timeline)), settledMutation = useSession((snapshot) => latestSettledMutation(snapshot.chat));
1372
1508
  return (0, import_react.useLayoutEffect)(() => {
1373
1509
  let document = marker.current?.ownerDocument;
1374
1510
  if (document === void 0) return;
@@ -1384,27 +1520,34 @@ function CreativeSplitBridge({ context }) {
1384
1520
  }, [sessionId]), (0, import_react.useLayoutEffect)(() => {
1385
1521
  let scroller = target?.parentElement;
1386
1522
  if (scroller == null) return;
1387
- let publishHeight = () => {
1523
+ let publishLayout = () => {
1388
1524
  scroller.style.setProperty("--oh-story-scroll-height", `${String(scroller.clientHeight)}px`);
1525
+ let layout = scroller.clientWidth < 620 ? "compact" : scroller.clientWidth < 900 ? "medium" : "wide";
1526
+ scroller.dataset.ohStoryLayout !== layout && (scroller.dataset.ohStoryLayout = layout);
1389
1527
  };
1390
- publishHeight();
1391
- let observer = new ResizeObserver(publishHeight);
1528
+ publishLayout();
1529
+ let observer = new ResizeObserver(publishLayout);
1392
1530
  return observer.observe(scroller), () => {
1393
- observer.disconnect(), scroller.style.removeProperty("--oh-story-scroll-height");
1531
+ observer.disconnect(), scroller.style.removeProperty("--oh-story-scroll-height"), delete scroller.dataset.ohStoryLayout;
1394
1532
  };
1395
1533
  }, [target]), /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1396
1534
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { ref: marker, className: "oh-story-bridge-marker", "aria-hidden": !0 }),
1397
- target === void 0 || sessionId === void 0 ? null : (0, import_react_dom.createPortal)(/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1535
+ target === void 0 ? null : (0, import_react_dom.createPortal)(/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1398
1536
  CreativeWorkbench,
1399
1537
  {
1400
1538
  sessionId,
1401
- runningCalls: snapshot?.runningCalls ?? EMPTY_CALLS,
1402
- projectedCalls: projection?.calls ?? []
1403
- },
1404
- sessionId
1539
+ runningCalls,
1540
+ partial,
1541
+ settledMutation,
1542
+ useStore,
1543
+ actions
1544
+ }
1405
1545
  ), target)
1406
1546
  ] });
1407
1547
  }
1548
+ function WorkbenchSeat({ SessionProvider, renderSlot }) {
1549
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SessionProvider, { children: () => renderSlot("oh-story.workspace", {}) });
1550
+ }
1408
1551
  function argsOf(block) {
1409
1552
  let raw = ("kind" in block ? block.call?.argsRaw : block.argsRaw) ?? "{}";
1410
1553
  try {
@@ -1433,11 +1576,18 @@ function RoleToolView({ block, inspect }) {
1433
1576
  ] });
1434
1577
  }
1435
1578
  function apply(context) {
1436
- context.slots.inject("shell.overlay", () => context.slots.register({
1437
- name: "shell.overlay",
1438
- id: "oh-story-workspace",
1439
- order: -100
1440
- }, () => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CreativeSplitBridge, { context }))), context.slots.inject("tool.call.toolview", () => context.slots.register({
1579
+ context.slots.inject("shell.overlay", () => {
1580
+ let disposeSeat = context.slots.register({
1581
+ name: "shell.overlay",
1582
+ id: "oh-story-workspace",
1583
+ order: -100,
1584
+ children: { "oh-story.workspace": { kind: "single", scope: "session" } }
1585
+ }, WorkbenchSeat), disposeWorkbench = context.slots.register({
1586
+ name: "oh-story.workspace",
1587
+ store: createWorkbenchStore
1588
+ }, CreativeSplitBridge);
1589
+ return [disposeSeat, disposeWorkbench];
1590
+ }), context.slots.inject("tool.call.toolview", () => context.slots.register({
1441
1591
  name: "tool.call.toolview",
1442
1592
  key: "oh_story_role"
1443
1593
  }, RoleToolView));