@sutrata/editor 0.1.2 → 0.3.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.
Files changed (125) hide show
  1. package/dist/ai/VoiceConfirmDialog.d.ts +1 -0
  2. package/dist/ai/VoiceConfirmDialog.js +6 -6
  3. package/dist/ai/VoiceToolbar.d.ts +1 -0
  4. package/dist/ai/VoiceToolbar.js +22 -16
  5. package/dist/ai/clean-response.d.ts +7 -0
  6. package/dist/ai/clean-response.js +58 -0
  7. package/dist/ai/format-transcript.d.ts +3 -0
  8. package/dist/ai/format-transcript.js +7 -0
  9. package/dist/ai/prompts.d.ts +8 -1
  10. package/dist/ai/prompts.js +34 -7
  11. package/dist/autocomplete/suggestions.js +1 -1
  12. package/dist/context/DocumentContext.d.ts +34 -5
  13. package/dist/context/DocumentContext.js +152 -51
  14. package/dist/editor/EditorView.js +35 -5
  15. package/dist/editor/SelectionMenu.js +5 -3
  16. package/dist/editor/editor-bus.d.ts +3 -0
  17. package/dist/editor/editor-bus.js +13 -0
  18. package/dist/editor/external-change.d.ts +11 -0
  19. package/dist/editor/external-change.js +105 -0
  20. package/dist/editor/external-decorations-plugin.d.ts +23 -0
  21. package/dist/editor/external-decorations-plugin.js +151 -0
  22. package/dist/editor/frontmatter-field-view.js +3 -0
  23. package/dist/editor/omitted-scene-plugin.d.ts +9 -0
  24. package/dist/editor/omitted-scene-plugin.js +44 -0
  25. package/dist/editor/plugins.js +4 -0
  26. package/dist/editor/private-metadata-plugin.d.ts +13 -0
  27. package/dist/editor/private-metadata-plugin.js +100 -0
  28. package/dist/editor/prosemirror-to-sutra.js +179 -74
  29. package/dist/editor/scene-heading-view.js +2 -0
  30. package/dist/editor/schema.js +74 -13
  31. package/dist/editor/sutra-to-prosemirror.d.ts +2 -0
  32. package/dist/editor/sutra-to-prosemirror.js +32 -34
  33. package/dist/extensions/ai-provider.d.ts +44 -0
  34. package/dist/extensions/ai-provider.js +27 -0
  35. package/dist/extensions/collab.d.ts +21 -0
  36. package/dist/extensions/collab.js +6 -0
  37. package/dist/extensions/command-registry.d.ts +49 -0
  38. package/dist/extensions/command-registry.js +28 -0
  39. package/dist/extensions/decorations.d.ts +48 -0
  40. package/dist/extensions/decorations.js +6 -0
  41. package/dist/extensions/export-registry.d.ts +59 -0
  42. package/dist/extensions/export-registry.js +10 -0
  43. package/dist/extensions/panel-registry.d.ts +30 -0
  44. package/dist/extensions/panel-registry.js +16 -0
  45. package/dist/extensions/registries.d.ts +15 -0
  46. package/dist/extensions/registries.js +12 -0
  47. package/dist/extensions/registry-store.d.ts +26 -0
  48. package/dist/extensions/registry-store.js +35 -0
  49. package/dist/extensions/session.d.ts +32 -0
  50. package/dist/extensions/session.js +20 -0
  51. package/dist/extensions/speech-provider.d.ts +35 -0
  52. package/dist/extensions/speech-provider.js +17 -0
  53. package/dist/extensions/storage-adapter.d.ts +36 -1
  54. package/dist/file/builtin-exporters.d.ts +9 -0
  55. package/dist/file/builtin-exporters.js +72 -0
  56. package/dist/file/docx-exporter.js +7 -4
  57. package/dist/file/docx-importer.d.ts +9 -0
  58. package/dist/file/docx-importer.js +47 -19
  59. package/dist/file/scene-number.d.ts +5 -0
  60. package/dist/file/scene-number.js +8 -0
  61. package/dist/file/workflow-reports.js +13 -7
  62. package/dist/findreplace/FindReplace.js +5 -2
  63. package/dist/import/AIImportDialog.d.ts +11 -0
  64. package/dist/import/AIImportDialog.js +112 -0
  65. package/dist/import/ai-import.d.ts +51 -0
  66. package/dist/import/ai-import.js +200 -0
  67. package/dist/import/apply-import.d.ts +7 -0
  68. package/dist/import/apply-import.js +35 -0
  69. package/dist/import/extract.d.ts +17 -0
  70. package/dist/import/extract.js +18 -0
  71. package/dist/index.d.ts +18 -3
  72. package/dist/index.js +7 -0
  73. package/dist/locales/bn.json +8 -0
  74. package/dist/locales/en.json +8 -0
  75. package/dist/locales/gu.json +8 -0
  76. package/dist/locales/hi.json +8 -0
  77. package/dist/locales/kn.json +8 -0
  78. package/dist/locales/ml.json +8 -0
  79. package/dist/locales/mr.json +8 -0
  80. package/dist/locales/or.json +8 -0
  81. package/dist/locales/pa.json +8 -0
  82. package/dist/locales/si.json +8 -0
  83. package/dist/locales/ta.json +8 -0
  84. package/dist/locales/te.json +8 -0
  85. package/dist/navigator/SceneNavigator.js +125 -131
  86. package/dist/navigator/StatisticsDialog.js +5 -1
  87. package/dist/navigator/scene-edits.d.ts +18 -0
  88. package/dist/navigator/scene-edits.js +143 -0
  89. package/dist/navigator/scene-list.js +4 -4
  90. package/dist/navigator/scene-numbering.d.ts +37 -0
  91. package/dist/navigator/scene-numbering.js +88 -0
  92. package/dist/settings/SettingsDialog.js +8 -120
  93. package/dist/shell/AppBar.js +25 -4
  94. package/dist/shell/AppShell.js +18 -4
  95. package/dist/shell/ElementToolbar.d.ts +5 -0
  96. package/dist/shell/ElementToolbar.js +35 -25
  97. package/dist/shell/LanguageStatus.js +6 -0
  98. package/dist/shell/StatusBar.js +11 -6
  99. package/dist/shell/builtin-commands.d.ts +10 -0
  100. package/dist/shell/builtin-commands.js +43 -0
  101. package/dist/shell/icons.d.ts +6 -0
  102. package/dist/shell/icons.js +6 -0
  103. package/dist/shell/use-commands.d.ts +7 -0
  104. package/dist/shell/use-commands.js +24 -0
  105. package/dist/shell/use-panel-context.d.ts +3 -0
  106. package/dist/shell/use-panel-context.js +39 -0
  107. package/dist/source/SourceView.js +39 -3
  108. package/dist/styles/screenplay.css +114 -1
  109. package/dist/titlepage/ExportDialog.d.ts +5 -0
  110. package/dist/titlepage/ExportDialog.js +59 -96
  111. package/dist/titlepage/frontmatter-form.js +1 -1
  112. package/package.json +4 -4
  113. package/dist/ai/OnboardingDialog.d.ts +0 -6
  114. package/dist/ai/OnboardingDialog.js +0 -115
  115. package/dist/ai/ai-client.d.ts +0 -71
  116. package/dist/ai/ai-client.js +0 -494
  117. package/dist/ai/generated-providers.json +0 -3696
  118. package/dist/ai/voice-service.d.ts +0 -15
  119. package/dist/ai/voice-service.js +0 -81
  120. package/dist/editor/character-view.d.ts +0 -35
  121. package/dist/editor/character-view.js +0 -112
  122. package/dist/editor/transliterate-input.d.ts +0 -10
  123. package/dist/editor/transliterate-input.js +0 -88
  124. package/dist/extensions/types.d.ts +0 -73
  125. package/dist/extensions/types.js +0 -12
@@ -0,0 +1,10 @@
1
+ import type { CommandContribution } from '../extensions/command-registry';
2
+ type Translate = (key: string) => string;
3
+ /**
4
+ * The editor's own commands, registered like an embedder's: undo/redo,
5
+ * element types (with their Alt+Shift shortcuts) and inline marks, all shown
6
+ * in the element toolbar. Mark and history keys are bound by the ProseMirror
7
+ * keymap, so they only carry a `shortcutHint`.
8
+ */
9
+ export declare function builtinCommands(t: Translate): CommandContribution[];
10
+ export {};
@@ -0,0 +1,43 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { makeSetBlock, makeSetOrToggleNote, makeToggleMark, undo, redo } from './toolbar-actions';
3
+ import { runCommand, activeBlockType } from '../editor/editor-bus';
4
+ import { UndoIcon, RedoIcon, SceneIcon, ActionIcon, CharacterIcon, DialogueIcon, ParentheticalIcon, TransitionIcon, CenteredIcon, LyricsIcon, NoteIcon, PageBreakIcon, SectionIcon, BoldIcon, ItalicIcon, UnderlineIcon, } from './icons';
5
+ const ELEMENTS = [
6
+ { type: 'scene_heading', sigil: '##', shortcut: 'Alt-Shift-H', Icon: SceneIcon, labelKey: 'toolbar.scene' },
7
+ { type: 'action', sigil: '(plain)', shortcut: 'Alt-Shift-A', Icon: ActionIcon, labelKey: 'toolbar.action' },
8
+ { type: 'character', sigil: '@', shortcut: 'Alt-Shift-C', Icon: CharacterIcon, labelKey: 'toolbar.character' },
9
+ { type: 'dialogue', sigil: '(after @)', shortcut: 'Alt-Shift-D', Icon: DialogueIcon, labelKey: 'toolbar.dialogue' },
10
+ { type: 'parenthetical', sigil: '( )', shortcut: 'Alt-Shift-P', Icon: ParentheticalIcon, labelKey: 'toolbar.parenthetical' },
11
+ { type: 'transition', sigil: '>>', shortcut: 'Alt-Shift-X', Icon: TransitionIcon, labelKey: 'toolbar.transition' },
12
+ { type: 'centered', sigil: '>> <<', Icon: CenteredIcon, labelKey: 'toolbar.centered' },
13
+ { type: 'lyrics', sigil: '~', shortcut: 'Alt-Shift-L', Icon: LyricsIcon, labelKey: 'toolbar.lyrics' },
14
+ { type: 'note', sigil: '[[ ]]', shortcut: 'Alt-Shift-N', Icon: NoteIcon, labelKey: 'toolbar.note' },
15
+ { type: 'page_break', sigil: '===', Icon: PageBreakIcon, labelKey: 'toolbar.pageBreak' },
16
+ { type: 'section', sigil: '#', Icon: SectionIcon, labelKey: 'toolbar.section' },
17
+ ];
18
+ /**
19
+ * The editor's own commands, registered like an embedder's: undo/redo,
20
+ * element types (with their Alt+Shift shortcuts) and inline marks, all shown
21
+ * in the element toolbar. Mark and history keys are bound by the ProseMirror
22
+ * keymap, so they only carry a `shortcutHint`.
23
+ */
24
+ export function builtinCommands(t) {
25
+ return [
26
+ { id: 'history.undo', label: t('toolbar.undo'), group: 'history', menu: 'toolbar', shortcutHint: 'Mod-Z', icon: s => _jsx(UndoIcon, { size: s }), run: () => runCommand(undo) },
27
+ { id: 'history.redo', label: t('toolbar.redo'), group: 'history', menu: 'toolbar', shortcutHint: 'Mod-Shift-Z', icon: s => _jsx(RedoIcon, { size: s }), run: () => runCommand(redo) },
28
+ ...ELEMENTS.map((el) => ({
29
+ id: `element.${el.type}`,
30
+ label: t(el.labelKey),
31
+ group: 'elements',
32
+ menu: 'toolbar',
33
+ sigil: el.sigil,
34
+ shortcut: el.shortcut,
35
+ icon: s => _jsx(el.Icon, { size: s }),
36
+ isActive: () => activeBlockType() === el.type,
37
+ run: () => runCommand(el.type === 'note' ? makeSetOrToggleNote() : makeSetBlock(el.type)),
38
+ })),
39
+ { id: 'mark.bold', label: t('toolbar.bold'), group: 'marks', menu: 'toolbar', shortcutHint: 'Mod-B', icon: s => _jsx(BoldIcon, { size: s }), run: () => runCommand(makeToggleMark('bold')) },
40
+ { id: 'mark.italic', label: t('toolbar.italic'), group: 'marks', menu: 'toolbar', shortcutHint: 'Mod-I', icon: s => _jsx(ItalicIcon, { size: s }), run: () => runCommand(makeToggleMark('italic')) },
41
+ { id: 'mark.underline', label: t('toolbar.underline'), group: 'marks', menu: 'toolbar', shortcutHint: 'Mod-U', icon: s => _jsx(UnderlineIcon, { size: s }), run: () => runCommand(makeToggleMark('underline')) },
42
+ ];
43
+ }
@@ -35,6 +35,12 @@ export declare const OverflowIcon: ({ size }: IconProps) => React.JSX.Element;
35
35
  export declare const EyeIcon: ({ size }: IconProps) => React.JSX.Element;
36
36
  export declare const EyeOffIcon: ({ size }: IconProps) => React.JSX.Element;
37
37
  export declare const LockIcon: ({ size }: IconProps) => React.JSX.Element;
38
+ /** Numbered list: "Renumber scenes". */
39
+ export declare const RenumberIcon: ({ size }: IconProps) => React.JSX.Element;
40
+ /** Circle with a slash: "Omit scene". */
41
+ export declare const OmitIcon: ({ size }: IconProps) => React.JSX.Element;
42
+ /** Curved arrow back: "Restore scene". */
43
+ export declare const RestoreIcon: ({ size }: IconProps) => React.JSX.Element;
38
44
  export declare const StatsIcon: ({ size }: IconProps) => React.JSX.Element;
39
45
  export declare const HistoryIcon: ({ size }: IconProps) => React.JSX.Element;
40
46
  export declare const MicIcon: ({ size }: IconProps) => React.JSX.Element;
@@ -42,6 +42,12 @@ export const OverflowIcon = ({ size = 14 }) => (_jsxs("svg", { width: size, heig
42
42
  export const EyeIcon = ({ size }) => (_jsxs("svg", { ...base(size), children: [_jsx("path", { d: "M1 8s3-6 7-6 7 6 7 6-3 6-7 6-7-6-7-6z" }), _jsx("circle", { cx: "8", cy: "8", r: "2.2" })] }));
43
43
  export const EyeOffIcon = ({ size }) => (_jsxs("svg", { ...base(size), children: [_jsx("path", { d: "M1 8s3-6 7-6 7 6 7 6-3 6-7 6-7-6-7-6z" }), _jsx("circle", { cx: "8", cy: "8", r: "2.2" }), _jsx("line", { x1: "2", y1: "2", x2: "14", y2: "14" })] }));
44
44
  export const LockIcon = ({ size }) => (_jsxs("svg", { ...base(size), children: [_jsx("rect", { x: "3", y: "7", width: "10", height: "7", rx: "1.5" }), _jsx("path", { d: "M4.5 7V4.5a3.5 3.5 0 017 0V7" })] }));
45
+ /** Numbered list: "Renumber scenes". */
46
+ export const RenumberIcon = ({ size }) => (_jsxs("svg", { ...base(size), children: [_jsx("path", { d: "M2.5 3.5h1v3M2.5 6.5h2" }), _jsx("path", { d: "M2.5 10h2l-2 2.5h2" }), _jsx("path", { d: "M7 4.5h6.5M7 11.5h6.5" })] }));
47
+ /** Circle with a slash: "Omit scene". */
48
+ export const OmitIcon = ({ size }) => (_jsxs("svg", { ...base(size), children: [_jsx("circle", { cx: "8", cy: "8", r: "5.5" }), _jsx("path", { d: "M4.2 11.8l7.6-7.6" })] }));
49
+ /** Curved arrow back: "Restore scene". */
50
+ export const RestoreIcon = ({ size }) => (_jsxs("svg", { ...base(size), children: [_jsx("path", { d: "M3.5 6.5h6a3.5 3.5 0 010 7H6" }), _jsx("path", { d: "M6 3.5l-2.5 3 2.5 3" })] }));
45
51
  export const StatsIcon = ({ size }) => (_jsxs("svg", { ...base(size), children: [_jsx("line", { x1: "12", y1: "14", x2: "12", y2: "4" }), _jsx("line", { x1: "8", y1: "14", x2: "8", y2: "7" }), _jsx("line", { x1: "4", y1: "14", x2: "4", y2: "10" })] }));
46
52
  export const HistoryIcon = ({ size }) => (_jsxs("svg", { ...base(size), children: [_jsx("circle", { cx: "8", cy: "8.5", r: "6" }), _jsx("polyline", { points: "8,5.5 8,8.5 10.5,10" })] }));
47
53
  export const MicIcon = ({ size }) => (_jsxs("svg", { ...base(size), children: [_jsx("rect", { x: "5.5", y: "2", width: "5", height: "8", rx: "2.5" }), _jsx("path", { d: "M3 7.5a5 5 0 0010 0" }), _jsx("line", { x1: "8", y1: "12.5", x2: "8", y2: "14.5" }), _jsx("line", { x1: "5.5", y1: "14.5", x2: "10.5", y2: "14.5" })] }));
@@ -0,0 +1,7 @@
1
+ import type { CommandContext, CommandContribution } from '../extensions/command-registry';
2
+ /** Built-in + registered commands available in this session (read-only sessions keep only `readOnly` ones). */
3
+ export declare function useCommands(): CommandContribution[];
4
+ /** A CommandContext for the current moment. */
5
+ export declare function useCommandContext(): () => CommandContext;
6
+ /** `Alt-Shift-H` → `Alt+Shift+H` for tooltips. */
7
+ export declare function displayShortcut(shortcut: string | undefined): string | undefined;
@@ -0,0 +1,24 @@
1
+ import { useMemo } from 'react';
2
+ import { useDocument } from '../context/DocumentContext';
3
+ import { useTranslation } from '../i18n/useTranslation';
4
+ import { useCanEdit } from '../extensions/session';
5
+ import { useCommandRegistry } from '../extensions/command-registry';
6
+ import { useRegistryList, withBuiltins } from '../extensions/registry-store';
7
+ import { getEditorView, getSourceView } from '../editor/editor-bus';
8
+ import { builtinCommands } from './builtin-commands';
9
+ /** Built-in + registered commands available in this session (read-only sessions keep only `readOnly` ones). */
10
+ export function useCommands() {
11
+ const { t } = useTranslation();
12
+ const canEdit = useCanEdit();
13
+ const registered = useRegistryList(useCommandRegistry());
14
+ return useMemo(() => withBuiltins(builtinCommands(t), registered).filter(c => canEdit || c.readOnly), [t, registered, canEdit]);
15
+ }
16
+ /** A CommandContext for the current moment. */
17
+ export function useCommandContext() {
18
+ const { mode, text, setText } = useDocument();
19
+ return () => ({ view: getEditorView(), sourceView: getSourceView(), mode, text, setText });
20
+ }
21
+ /** `Alt-Shift-H` → `Alt+Shift+H` for tooltips. */
22
+ export function displayShortcut(shortcut) {
23
+ return shortcut?.split(/[-+](?=.)/).join('+');
24
+ }
@@ -0,0 +1,3 @@
1
+ import type { PanelRenderContext } from '../extensions/panel-registry';
2
+ /** The context panels render with; re-computed on document and selection changes. */
3
+ export declare function usePanelContext(): PanelRenderContext;
@@ -0,0 +1,39 @@
1
+ import { useEffect, useMemo, useState } from 'react';
2
+ import { useDocument } from '../context/DocumentContext';
3
+ import { getEditorView, getSourceView, subscribe } from '../editor/editor-bus';
4
+ import { buildSceneList } from '../navigator/scene-list';
5
+ /** Id of the scene containing the caret (formatted or source editor), if it has one. */
6
+ function currentSceneId(text) {
7
+ const view = getEditorView();
8
+ if (view) {
9
+ const caret = view.state.selection.from;
10
+ let id = null;
11
+ view.state.doc.forEach((node, offset) => {
12
+ if (offset < caret && node.type.name === 'scene_heading')
13
+ id = node.attrs['id'] ?? null;
14
+ });
15
+ return id;
16
+ }
17
+ const source = getSourceView();
18
+ if (source) {
19
+ const caret = source.state.selection.main.head;
20
+ let id = null;
21
+ for (const scene of buildSceneList(text))
22
+ if (scene.textOffset <= caret)
23
+ id = scene.id;
24
+ return id;
25
+ }
26
+ return null;
27
+ }
28
+ /** The context panels render with; re-computed on document and selection changes. */
29
+ export function usePanelContext() {
30
+ const { ast, text } = useDocument();
31
+ const [activeSceneId, setActiveSceneId] = useState(null);
32
+ useEffect(() => {
33
+ const update = () => setActiveSceneId(currentSceneId(text));
34
+ update();
35
+ return subscribe(update);
36
+ }, [text]);
37
+ const sceneIds = useMemo(() => ast.children.flatMap(n => (n.type === 'scene-heading' && n.id ? [n.id] : [])), [ast]);
38
+ return useMemo(() => ({ ast, sceneIds, activeSceneId }), [ast, sceneIds, activeSceneId]);
39
+ }
@@ -1,14 +1,17 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useEffect, useRef } from 'react';
3
- import { EditorState } from '@codemirror/state';
3
+ import { Annotation, Compartment, EditorState, Transaction } from '@codemirror/state';
4
4
  import { EditorView, keymap } from '@codemirror/view';
5
5
  import { defaultKeymap, history, historyKeymap } from '@codemirror/commands';
6
6
  import { HighlightStyle, syntaxHighlighting } from '@codemirror/language';
7
7
  import { tags } from '@lezer/highlight';
8
8
  import { sutraLanguage } from './sutra-lang';
9
9
  import { useDocument } from '../context/DocumentContext';
10
- import { setSourceView } from '../editor/editor-bus';
10
+ import { setSourceView, takeExternalText } from '../editor/editor-bus';
11
11
  import { sourceFindHighlightExtension } from './find-highlight-source';
12
+ import { useCanEdit } from '../extensions/session';
13
+ /** Marks the transaction that mirrors an external text change into the view. */
14
+ const externalSync = Annotation.define();
12
15
  // Light theme matching the app's warm off-white palette.
13
16
  // No internal scroll — the editor grows to content height and .cs-main scrolls.
14
17
  const sutraTheme = EditorView.theme({
@@ -54,6 +57,12 @@ const sutraHighlight = HighlightStyle.define([
54
57
  ]);
55
58
  export function SourceView() {
56
59
  const { text, setText } = useDocument();
60
+ // Read-only sessions: the view is not editable and doc changes other than
61
+ // mirroring external text are filtered out (see the transactionFilter).
62
+ const canEdit = useCanEdit();
63
+ const canEditRef = useRef(canEdit);
64
+ canEditRef.current = canEdit;
65
+ const editableCompartment = useRef(new Compartment());
57
66
  const containerRef = useRef(null);
58
67
  const viewRef = useRef(null);
59
68
  // Track externally-driven text so we don't feed our own updates back
@@ -72,8 +81,11 @@ export function SourceView() {
72
81
  syntaxHighlighting(sutraHighlight),
73
82
  sourceFindHighlightExtension,
74
83
  EditorView.lineWrapping,
84
+ editableCompartment.current.of([EditorView.editable.of(canEdit), EditorState.readOnly.of(!canEdit)]),
85
+ EditorState.transactionFilter.of(tr => tr.docChanged && !canEditRef.current && !tr.annotation(externalSync) ? [] : tr),
75
86
  EditorView.updateListener.of(update => {
76
- if (update.docChanged) {
87
+ // Mirrored text (externalSync) is already the document's text.
88
+ if (update.docChanged && !update.transactions.some(t => t.annotation(externalSync))) {
77
89
  const newText = update.state.doc.toString();
78
90
  lastTextRef.current = newText;
79
91
  setText(newText);
@@ -98,9 +110,33 @@ export function SourceView() {
98
110
  if (!view || text === lastTextRef.current)
99
111
  return;
100
112
  lastTextRef.current = text;
113
+ if (takeExternalText(text)) {
114
+ // From storage: change only what differs, keep the cursor, not undoable.
115
+ const old = view.state.doc.toString();
116
+ let from = 0;
117
+ while (from < old.length && from < text.length && old[from] === text[from])
118
+ from++;
119
+ let endOld = old.length;
120
+ let endNew = text.length;
121
+ while (endOld > from && endNew > from && old[endOld - 1] === text[endNew - 1]) {
122
+ endOld--;
123
+ endNew--;
124
+ }
125
+ view.dispatch({
126
+ changes: { from, to: endOld, insert: text.slice(from, endNew) },
127
+ annotations: [externalSync.of(true), Transaction.addToHistory.of(false)],
128
+ });
129
+ return;
130
+ }
101
131
  view.dispatch({
102
132
  changes: { from: 0, to: view.state.doc.length, insert: text },
133
+ annotations: externalSync.of(true),
103
134
  });
104
135
  }, [text]);
136
+ useEffect(() => {
137
+ viewRef.current?.dispatch({
138
+ effects: editableCompartment.current.reconfigure([EditorView.editable.of(canEdit), EditorState.readOnly.of(!canEdit)]),
139
+ });
140
+ }, [canEdit]);
105
141
  return _jsx("div", { ref: containerRef, className: "cs-source-view" });
106
142
  }
@@ -31,6 +31,10 @@
31
31
  --cs-ui-accent-badge: #d97706;
32
32
  --cs-ui-accent-border: #fcd34d;
33
33
 
34
+ /* Warnings (e.g. a scene without an {#id}) — distinct from the amber accent */
35
+ --cs-ui-warning: #c2410c;
36
+ --cs-ui-warning-subtle: #fff7ed;
37
+
34
38
  /* Screenplay note tokens */
35
39
  --cs-note-bg: #fffbeb;
36
40
  --cs-note-border-color: #d97706;
@@ -91,6 +95,45 @@ html, body, #root { height: 100%; margin: 0; padding: 0; overflow: hidden; }
91
95
  flex-shrink: 0;
92
96
  }
93
97
 
98
+ /* Sidebar with embedder tabs (only when PanelRegistry has 'sidebar' panels) */
99
+ .cs-sidebar {
100
+ width: min(33%,400px);
101
+ min-width: 180px;
102
+ max-width: 480px;
103
+ display: flex;
104
+ flex-direction: column;
105
+ overflow: hidden;
106
+ flex-shrink: 0;
107
+ border-right: 1px solid #ddd;
108
+ background: #f8f8f6;
109
+ }
110
+ .cs-sidebar > .cs-navigator { width: auto; min-width: 0; max-width: none; flex: 1; border-right: none; }
111
+ .cs-sidebar-tabs { display: flex; gap: 2px; padding: 4px 6px 0; border-bottom: 1px solid var(--cs-ui-border-light, #e2e0d6); }
112
+ .cs-sidebar-tab {
113
+ background: none; border: none; border-bottom: 2px solid transparent; padding: 6px 8px;
114
+ font-size: 12px; font-weight: 600; color: var(--cs-ui-text-light-secondary, #59574f); cursor: pointer;
115
+ }
116
+ .cs-sidebar-tab-active { color: var(--cs-ui-text-light-primary, #1c1b18); border-bottom-color: var(--cs-ui-accent, #c4760a); }
117
+ .cs-sidebar-panel { flex: 1; overflow: auto; padding: 8px 12px; font-size: 12px; }
118
+
119
+ /* Inspector (only when PanelRegistry has 'inspector' panels) */
120
+ .cs-inspector {
121
+ width: min(28%, 320px);
122
+ min-width: 200px;
123
+ flex-shrink: 0;
124
+ overflow-y: auto;
125
+ border-left: 1px solid var(--cs-ui-border-light, #e2e0d6);
126
+ background: var(--cs-ui-bg-panel, #fcfbfa);
127
+ font-size: 12px;
128
+ }
129
+ .cs-inspector-section { padding: 10px 12px; border-bottom: 1px solid var(--cs-ui-border-light-subtle, #eeece4); }
130
+ .cs-inspector-title { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: var(--cs-ui-text-light-secondary, #59574f); margin-bottom: 6px; }
131
+ @media (max-width: 640px) {
132
+ .cs-inspector { display: none; }
133
+ }
134
+ .cs-ab-text-label, .cs-tb-text-label { font-size: 12px; font-weight: 600; }
135
+ .cs-ab-panel { display: inline-flex; align-items: center; }
136
+
94
137
  .cs-nav-count {
95
138
  background: #e0e0dd;
96
139
  border-radius: 10px;
@@ -160,6 +203,36 @@ html, body, #root { height: 100%; margin: 0; padding: 0; overflow: hidden; }
160
203
  border-bottom-color: #1976d2;
161
204
  }
162
205
 
206
+ .cs-nav-scene-missing-id {
207
+ box-shadow: inset 3px 0 0 var(--cs-ui-warning, #c2410c);
208
+ }
209
+
210
+ .cs-nav-missing-id-icon {
211
+ font-size: 12px;
212
+ line-height: 1.4;
213
+ color: var(--cs-ui-warning, #c2410c);
214
+ flex-shrink: 0;
215
+ align-self: flex-start;
216
+ padding-left: 8px;
217
+ cursor: help;
218
+ }
219
+
220
+ /* Scene number needs renumbering (format spec §7.4) */
221
+ .cs-nav-scene-number-issue { box-shadow: inset 3px 0 0 var(--cs-ui-warning, #c2410c); }
222
+ .cs-nav-num-issue {
223
+ color: var(--cs-ui-warning, #c2410c) !important;
224
+ background: var(--cs-ui-warning-subtle, #fff7ed);
225
+ border-radius: 3px;
226
+ padding: 0 4px;
227
+ cursor: help;
228
+ }
229
+ .cs-nav-scene-side { display: flex; align-items: flex-start; gap: 2px; flex-shrink: 0; }
230
+ .cs-nav-omit-btn { opacity: 0; transition: opacity 0.1s; }
231
+ .cs-nav-item:hover .cs-nav-omit-btn,
232
+ .cs-nav-item:focus-within .cs-nav-omit-btn,
233
+ .cs-nav-omit-btn:focus-visible { opacity: 1; }
234
+ .cs-nav-scene-omitted .cs-nav-heading { text-decoration: line-through; opacity: 0.7; }
235
+
163
236
  .cs-nav-scene-num {
164
237
  font-size: 12px;
165
238
  font-weight: 600;
@@ -434,6 +507,12 @@ html, body, #root { height: 100%; margin: 0; padding: 0; overflow: hidden; }
434
507
  margin-left: max(10%, 8px);
435
508
  }
436
509
 
510
+ /* Tool-private `x-` keys (format spec §7.1) stay in the text but are not shown */
511
+ .cs-scene-metadata[data-private],
512
+ .cs-fm-field[data-private] {
513
+ display: none;
514
+ }
515
+
437
516
  /* Synopsis renders as italic prose, visually distinct from key-value metadata */
438
517
  .cs-scene-metadata[data-key="synopsis"] {
439
518
  font-size: 10pt;
@@ -2675,9 +2754,12 @@ html, body, #root { height: 100%; margin: 0; padding: 0; overflow: hidden; }
2675
2754
  padding: 10px 12px;
2676
2755
  font-size: 13px;
2677
2756
  color: var(--cs-ui-text-light-primary, #1c1b18);
2678
- min-height: 140px;
2679
2757
  line-height: 1.5;
2680
2758
  white-space: pre-wrap;
2759
+ /* Long selections scroll inside the pane; .cs-dialog clips overflow. */
2760
+ box-sizing: border-box;
2761
+ height: min(50vh, 420px);
2762
+ overflow-y: auto;
2681
2763
  }
2682
2764
  .cs-voice-box-formatted {
2683
2765
  width: 100%;
@@ -2689,6 +2771,8 @@ html, body, #root { height: 100%; margin: 0; padding: 0; overflow: hidden; }
2689
2771
  font-size: 13px;
2690
2772
  font-family: 'Courier Prime', Courier, monospace;
2691
2773
  color: var(--cs-ui-text-light-primary, #1c1b18);
2774
+ /* Same height as the raw pane so the two read side by side. */
2775
+ height: min(50vh, 420px);
2692
2776
  min-height: 140px;
2693
2777
  line-height: 1.4;
2694
2778
  resize: vertical;
@@ -3004,3 +3088,32 @@ html, body, #root { height: 100%; margin: 0; padding: 0; overflow: hidden; }
3004
3088
  .cs-tp-row > :first-child { grid-column: 1 / -1; }
3005
3089
  .cs-tp-add { margin-left: 0; }
3006
3090
  }
3091
+
3092
+ /* AI-assisted import dialog */
3093
+ .cs-ai-import-body { padding: 14px 16px; max-height: 80vh; overflow-y: auto; font-size: 13px; }
3094
+ .cs-ai-import-hint { margin: 0 0 10px; color: var(--cs-ui-text-light-secondary, #59574f); display: flex; align-items: center; flex-wrap: wrap; gap: 4px; }
3095
+ .cs-ai-import-error { background: #fef2f2; border: 1px solid #fecaca; color: #991b1b; padding: 8px 10px; border-radius: 4px; margin-bottom: 10px; }
3096
+ .cs-ai-import-paste { width: 100%; box-sizing: border-box; font-family: 'Courier New', monospace; font-size: 12px; padding: 8px; border: 1px solid var(--cs-ui-border-light, #e2e0d6); border-radius: 4px; }
3097
+ .cs-ai-import-policy { background: var(--cs-ui-bg-panel-subtle, #f4f2ea); border-radius: 4px; padding: 10px; font-size: 12px; margin-bottom: 10px; white-space: pre-wrap; }
3098
+ .cs-ai-import-agree { display: flex; gap: 8px; align-items: center; font-size: 12px; }
3099
+ .cs-ai-import-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 14px; }
3100
+ .cs-ai-import-warnings { margin-bottom: 10px; }
3101
+ .cs-ai-import-review { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
3102
+ .cs-ai-import-col-title { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: var(--cs-ui-text-light-secondary, #59574f); margin-bottom: 6px; }
3103
+ .cs-ai-import-source, .cs-ai-import-block pre { white-space: pre-wrap; word-break: break-word; font-family: 'Courier New', monospace; font-size: 12px; margin: 0; }
3104
+ .cs-ai-import-source { max-height: 60vh; overflow: auto; background: var(--cs-ui-bg-panel-subtle, #f4f2ea); padding: 8px; border-radius: 4px; }
3105
+ .cs-ai-import-blocks { list-style: none; margin: 0; padding: 0; max-height: 60vh; overflow: auto; }
3106
+ .cs-ai-import-block { display: grid; grid-template-columns: 150px 1fr; gap: 8px; align-items: start; padding: 6px 0; border-bottom: 1px solid var(--cs-ui-border-light-subtle, #eeece4); }
3107
+ @media (max-width: 640px) {
3108
+ .cs-ai-import-review { grid-template-columns: 1fr; }
3109
+ .cs-ai-import-block { grid-template-columns: 1fr; }
3110
+ }
3111
+
3112
+ /* Omitted scene (format spec §7.4) */
3113
+ .cs-scene-omitted .cs-scene-heading-text { text-decoration: line-through; opacity: 0.6; }
3114
+ .cs-omitted-badge {
3115
+ display: inline-block; margin-left: 12px; padding: 0 6px; border-radius: 3px;
3116
+ font-size: 0.75em; font-weight: 700; letter-spacing: 0.08em; text-decoration: none;
3117
+ color: var(--cs-ui-warning, #c2410c); background: var(--cs-ui-warning-subtle, #fff7ed);
3118
+ user-select: none; vertical-align: middle;
3119
+ }
@@ -1 +1,6 @@
1
+ /**
2
+ * Renders every export format from one list: the editor's built-ins
3
+ * (file/builtin-exporters.ts) plus the embedder's ExportRegistry entries.
4
+ * Screenplay formats are cards; reports are grouped by `family` with chips.
5
+ */
1
6
  export declare function ExportDialog(): import("react").JSX.Element;