dsh-ptc-plus 0.3.2 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +65 -78
- package/README.md +65 -78
- package/assets/ptc-plus-bindings-en.png +0 -0
- package/assets/ptc-plus-bindings-zh.png +0 -0
- package/assets/ptc-plus-repair-en.png +0 -0
- package/assets/ptc-plus-repair-zh.png +0 -0
- package/assets/ptc-plus-repl-workspace-en.png +0 -0
- package/assets/ptc-plus-repl-workspace-zh.png +0 -0
- package/assets/ptc-plus-settings-en.png +0 -0
- package/assets/ptc-plus-settings-zh.png +0 -0
- package/client.js +30596 -742
- package/compiler-amaro.cjs +5 -0
- package/compiler-amaro.cjs.map +6 -0
- package/compiler-asset-124f80c1bba92ff5fe79cfec6e8ad518f476de4a42514cae3919538887f510e8.txt +1 -0
- package/compiler-assets.json +3 -0
- package/compiler-core.cjs +554 -0
- package/compiler-core.cjs.map +6 -0
- package/compiler-platform.cjs +2 -0
- package/compiler-platform.cjs.map +6 -0
- package/compiler-typescript.cjs +388 -0
- package/compiler-typescript.cjs.map +6 -0
- package/docs/README.md +6 -0
- package/docs/adr/0004-declare-program-bindings-at-the-host.md +1 -1
- package/docs/adr/0006-rewrite-module-syntax-with-an-ast.md +5 -3
- package/docs/adr/0007-separate-worker-transport-from-session-semantics.md +1 -1
- package/docs/adr/0009-bound-expensive-acceptance-default.md +2 -0
- package/docs/adr/0010-session-log-derived-recovery-tips.md +59 -5
- package/docs/adr/0011-isolate-the-local-recovery-tip-provider.md +1 -1
- package/docs/adr/0012-preserve-mixed-destructuring-initialization.md +3 -1
- package/docs/adr/0013-isolate-repl-redeclaration-convenience.md +3 -1
- package/docs/adr/0014-persist-cell-rewrite-policy.md +6 -2
- package/docs/adr/0015-preserve-source-positions-through-rewrites.md +1 -1
- package/docs/adr/0016-archive-resolved-review-findings.md +1 -1
- package/docs/adr/0017-track-the-latest-dsh-public-surface.md +49 -0
- package/docs/adr/0018-use-versioned-fixture-for-ab-benchmark.md +3 -3
- package/docs/adr/0019-plugin-settings-and-kill-switch.md +5 -3
- package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +2 -2
- package/docs/adr/0021-separate-host-bootstrap-from-session-runtime.md +12 -0
- package/docs/adr/0023-global-user-bindings.md +104 -0
- package/docs/adr/0024-repl-console-observation.md +33 -0
- package/docs/adr/0025-use-versioned-logical-binding-identities.md +119 -0
- package/docs/adr/0026-persist-the-delivery-verdict-against-the-verified-candidate.md +21 -0
- package/docs/adr/0027-run-the-session-kernel-in-a-killable-helper-process.md +37 -0
- package/docs/adr/0028-attach-to-the-host-ptc-execution-seam.md +103 -0
- package/docs/adr/0029-keep-plugin-unavailability-out-of-the-host-agent-lifecycle.md +27 -0
- package/docs/adr/0030-serve-both-typert-codec-generations-from-one-codec.md +27 -0
- package/docs/architecture.md +73 -21
- package/docs/binding-review-placement-design.md +200 -0
- package/docs/capability-projection.md +1 -1
- package/docs/client-ui.md +95 -19
- package/docs/durability-design.md +25 -10
- package/docs/evaluation.md +78 -6
- package/docs/installation.md +41 -6
- package/docs/publishing.md +5 -3
- package/docs/runtime-reference.md +72 -25
- package/docs/semantic-validation.md +29 -0
- package/docs/stateful-binding-updates-proposal.md +755 -0
- package/docs/user-bindings.en.md +64 -0
- package/docs/user-bindings.md +64 -0
- package/docs/value-wire.md +3 -3
- package/docs/verification.md +57 -0
- package/index.js +369 -92
- package/internal/ast-traversal.js +5 -2
- package/internal/binding-descriptors.js +1 -0
- package/internal/binding-pattern-traversal.js +25 -0
- package/internal/binding-pattern.js +26 -29
- package/internal/binding-source-analysis.js +391 -0
- package/internal/binding-update-policy.js +66 -0
- package/internal/bounded-worker-round.js +56 -0
- package/internal/callable-definition-environment.js +42 -0
- package/internal/callable-source-catalog.js +129 -0
- package/internal/callable-source-encoding.js +47 -0
- package/internal/callable-source-facts.js +459 -0
- package/internal/callable-source-format.js +2 -0
- package/internal/callable-source-lowering.js +123 -0
- package/internal/cell-analysis-contract.js +25 -0
- package/internal/cell-analysis.js +240 -290
- package/internal/cell-completion.js +15 -0
- package/internal/cell-error.js +7 -0
- package/internal/cell-parser.js +66 -0
- package/internal/cell-rewriter.js +3 -677
- package/internal/commonjs-export-evidence.js +63 -0
- package/internal/commonjs-source-evidence.js +72 -0
- package/internal/compiler-amaro.cjs +2 -0
- package/internal/compiler-compression.js +12 -0
- package/internal/compiler-data.js +58 -0
- package/internal/compiler-descriptors.js +54 -0
- package/internal/compiler-dynamic-cache.js +48 -0
- package/internal/compiler-entry.js +35 -0
- package/internal/compiler-generated-names.js +17 -0
- package/internal/compiler-intrinsics.js +84 -0
- package/internal/compiler-module-links.js +34 -0
- package/internal/compiler-operations.js +61 -0
- package/internal/compiler-platform-factory.js +84 -0
- package/internal/compiler-platform.js +136 -0
- package/internal/compiler-record-roles.js +53 -0
- package/internal/compiler-region-output.js +259 -0
- package/internal/compiler-resource-lowering.js +103 -0
- package/internal/compiler-scope-facts.js +115 -0
- package/internal/compiler-service.js +109 -0
- package/internal/compiler-source-cache.js +32 -0
- package/internal/compiler-source-pieces.js +93 -0
- package/internal/compiler-source-regions.js +132 -0
- package/internal/compiler-storage-source.js +54 -0
- package/internal/compiler-text.js +15 -0
- package/internal/compiler-typescript.cjs +2 -0
- package/internal/config-spec.js +83 -27
- package/internal/cordis-tools-owner.js +130 -21
- package/internal/diagnostic.js +27 -1
- package/internal/direct-surface-owner.js +76 -23
- package/internal/dynamic-binding-evidence.js +16 -0
- package/internal/dynamic-environment-compiler.js +779 -0
- package/internal/dynamic-environment-integration.js +87 -0
- package/internal/dynamic-environment-runtime.js +362 -0
- package/internal/dynamic-native-calls.js +150 -0
- package/internal/dynamic-native-runtime.js +248 -0
- package/internal/dynamic-scope-analysis.js +90 -0
- package/internal/edit-transport-owner.js +27 -2
- package/internal/execution-seam-compat.js +193 -0
- package/internal/failure-reporting.js +118 -18
- package/internal/host-rpc.js +66 -0
- package/internal/isolated-worker.js +548 -0
- package/internal/kernel-child.js +139 -0
- package/internal/kernel-worker.js +904 -120
- package/internal/language-semantics.js +14 -0
- package/internal/legacy-binding-catalog.js +48 -0
- package/internal/legacy-cell-analysis.js +600 -0
- package/internal/legacy-cell-parser.js +50 -0
- package/internal/legacy-cell-rewriter.js +631 -0
- package/internal/legacy-repl-convenience.js +291 -0
- package/internal/managed-module-operations.js +170 -0
- package/internal/module-compilation.js +321 -0
- package/internal/module-runtime-platform.js +19 -0
- package/internal/module-transform-contract.js +12 -0
- package/internal/native-await-declarations.js +68 -0
- package/internal/native-root-compilation.js +54 -0
- package/internal/native-root-dynamic.js +67 -0
- package/internal/recovery-tips.js +27 -15
- package/internal/rejected-cell-editor.js +13 -8
- package/internal/repl-convenience.js +2 -289
- package/internal/repl-memory-projection.js +81 -12
- package/internal/repl-observation-interest.js +70 -0
- package/internal/repl-rewrite-contract.js +6 -0
- package/internal/repl-scope-normalizer.js +2091 -0
- package/internal/repl-value-observer.js +114 -0
- package/internal/rpc-contract.js +52 -0
- package/internal/runtime-bridge-owner.js +172 -28
- package/internal/runtime-config.js +48 -52
- package/internal/runtime-contexts.js +82 -10
- package/internal/runtime-messages.js +111 -0
- package/internal/session-cell-executor.js +401 -87
- package/internal/session-journal-recovery.js +684 -0
- package/internal/session-journal-schema.js +106 -0
- package/internal/session-journal.js +184 -654
- package/internal/session-log-view.js +42 -9
- package/internal/session-runtime.js +256 -95
- package/internal/session-state.js +247 -71
- package/internal/source-position-map.js +211 -71
- package/internal/stateful-module-compiler.js +154 -0
- package/internal/stateful-module-runtime.js +303 -0
- package/internal/stateful-root-compiler.js +598 -0
- package/internal/stateful-root-runtime.js +340 -0
- package/internal/typert-codec-compat.js +28 -0
- package/internal/typescript-transform.js +29 -0
- package/internal/user-binding-authoring.js +70 -0
- package/internal/user-binding-console-worker.js +184 -0
- package/internal/user-binding-console.js +148 -0
- package/internal/user-binding-draft-projection.js +335 -0
- package/internal/user-binding-model-context.js +31 -0
- package/internal/user-binding-runner.js +56 -0
- package/internal/user-bindings-owner.js +1065 -0
- package/internal/user-bindings-store.js +293 -0
- package/internal/user-bindings.js +353 -0
- package/internal/value-wire-schema.js +28 -0
- package/internal/value-wire.js +29 -20
- package/internal/worker-client.js +57 -67
- package/internal/worker-environment.js +45 -0
- package/internal/worker-output-relay.js +29 -0
- package/internal/worker-shutdown.js +14 -0
- package/package.json +75 -7
- package/src/client-activity.js +88 -53
- package/src/client-authoring-view.js +580 -0
- package/src/client-binding-review.js +230 -0
- package/src/client-bindings-data.js +29 -0
- package/src/client-catalog.js +127 -0
- package/src/client-code-editor.js +84 -0
- package/src/client-console.js +112 -0
- package/src/client-copy.js +370 -0
- package/src/client-feature-gates.js +62 -0
- package/src/client-host-compat.js +150 -0
- package/src/client-indicator-view.js +267 -0
- package/src/client-repl-view.js +292 -0
- package/src/client-rpc.js +20 -0
- package/src/client-settings-view.js +221 -0
- package/src/client-styles.js +106 -0
- package/src/client-tool-view.js +108 -0
- package/src/client-transport.js +159 -0
- package/src/client-workbench.js +521 -0
- package/src/client.js +233 -615
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hover dwell before the card opens: the indicator shares the session header with
|
|
3
|
+
* native actions, so a pointer merely crossing it must not open a card over the
|
|
4
|
+
* conversation. Click and keyboard focus stay immediate.
|
|
5
|
+
*/
|
|
6
|
+
const HOVER_DWELL_MS = 150
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Grace before a pointer-dismissed card closes. It carries the pointer across the
|
|
10
|
+
* 8px trigger-card gap without leaving the card behind a pointer that moved on.
|
|
11
|
+
*/
|
|
12
|
+
const CLOSE_GRACE_MS = 200
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Header indicator for PTC Plus sessions: a popover trigger whose card is
|
|
16
|
+
* supplied by the REPL view. Popover open/placement logic is shared with the
|
|
17
|
+
* card rather than re-implemented here.
|
|
18
|
+
*/
|
|
19
|
+
export function createIndicatorView(React, deps) {
|
|
20
|
+
const {
|
|
21
|
+
featureEnabled, useSessionPreset, sessionUsesPtcPreset,
|
|
22
|
+
normalizeReplMemorySnapshot, unavailableReplMemorySnapshot,
|
|
23
|
+
ReplMemoryCard, replPopoverIsOpen, placeReplPopover,
|
|
24
|
+
catalogOwner, subscribeReset,
|
|
25
|
+
} = deps
|
|
26
|
+
const h = React.createElement
|
|
27
|
+
|
|
28
|
+
function PTCPlusSessionIndicator({
|
|
29
|
+
sessionId, t, useProjection, useSessions, usePtcSettings, callUserBindings,
|
|
30
|
+
}) {
|
|
31
|
+
const preset = useSessionPreset({ sessionId, useProjection, useSessions })
|
|
32
|
+
const projectionMemory = useProjection('ptcPlusRepl')
|
|
33
|
+
const settings = usePtcSettings(snapshot => snapshot)
|
|
34
|
+
const resolvedSessionId = sessionId
|
|
35
|
+
const globalEnabled = featureEnabled(settings, 'bindings')
|
|
36
|
+
const identity = JSON.stringify([sessionId, globalEnabled])
|
|
37
|
+
// The catalog is read through its owner: this surface keeps no epoch of its
|
|
38
|
+
// own, so a read here cannot invalidate another surface's request.
|
|
39
|
+
const catalogSource = React.useMemo(() => catalogOwner.claim(), [catalogOwner])
|
|
40
|
+
const catalogState = React.useSyncExternalStore(catalogSource.subscribe, catalogSource.getSnapshot)
|
|
41
|
+
const globalBindings = catalogState.catalog ?? undefined
|
|
42
|
+
const refreshGlobalBindings = React.useCallback(() => {
|
|
43
|
+
if (!globalEnabled) return
|
|
44
|
+
void catalogSource.read()
|
|
45
|
+
}, [globalEnabled, catalogSource])
|
|
46
|
+
React.useEffect(() => () => catalogSource.release(), [catalogSource])
|
|
47
|
+
React.useEffect(() => {
|
|
48
|
+
// Session and feature identity own this surface's catalog lifetime.
|
|
49
|
+
if (!globalEnabled) { catalogSource.reset(); return }
|
|
50
|
+
void catalogSource.read()
|
|
51
|
+
}, [identity, globalEnabled, catalogSource])
|
|
52
|
+
React.useEffect(() => subscribeReset(() => catalogSource.reset()), [catalogSource])
|
|
53
|
+
const triggerRef = React.useRef(null)
|
|
54
|
+
const shellRef = React.useRef(null)
|
|
55
|
+
const popoverRef = React.useRef(null)
|
|
56
|
+
const openTimer = React.useRef(undefined)
|
|
57
|
+
const closeTimer = React.useRef(undefined)
|
|
58
|
+
const placementFrame = React.useRef(undefined)
|
|
59
|
+
// The card leaves with the pointer unless a click pinned it. Keyboard modality
|
|
60
|
+
// separates a reader focusing the trigger from the focus a pointer press leaves
|
|
61
|
+
// behind, which must not turn a hovering card into a resident one.
|
|
62
|
+
const pinned = React.useRef(false)
|
|
63
|
+
const keyboard = React.useRef(false)
|
|
64
|
+
// Restoring focus to the trigger is bookkeeping, not a reader asking for the
|
|
65
|
+
// card back, so it must not read as a keyboard request to open it again.
|
|
66
|
+
const restoringFocus = React.useRef(false)
|
|
67
|
+
const [expanded, setExpanded] = React.useState(false)
|
|
68
|
+
React.useEffect(() => {
|
|
69
|
+
const markKeyboard = () => { keyboard.current = true }
|
|
70
|
+
const markPointer = () => { keyboard.current = false }
|
|
71
|
+
document.addEventListener('keydown', markKeyboard, true)
|
|
72
|
+
document.addEventListener('pointerdown', markPointer, true)
|
|
73
|
+
return () => {
|
|
74
|
+
document.removeEventListener('keydown', markKeyboard, true)
|
|
75
|
+
document.removeEventListener('pointerdown', markPointer, true)
|
|
76
|
+
}
|
|
77
|
+
}, [])
|
|
78
|
+
const cancelOpen = React.useCallback(() => {
|
|
79
|
+
if (openTimer.current === undefined) return
|
|
80
|
+
clearTimeout(openTimer.current)
|
|
81
|
+
openTimer.current = undefined
|
|
82
|
+
}, [])
|
|
83
|
+
const cancelClose = React.useCallback(() => {
|
|
84
|
+
if (closeTimer.current === undefined) return
|
|
85
|
+
clearTimeout(closeTimer.current)
|
|
86
|
+
closeTimer.current = undefined
|
|
87
|
+
}, [])
|
|
88
|
+
const positionPopover = React.useCallback(() => {
|
|
89
|
+
if (!replPopoverIsOpen(popoverRef.current)) return
|
|
90
|
+
placeReplPopover(triggerRef.current, popoverRef.current)
|
|
91
|
+
}, [])
|
|
92
|
+
// A captured scroll fires per event and every placement forces layout, so
|
|
93
|
+
// several can collapse into one frame.
|
|
94
|
+
const schedulePlacement = React.useCallback(() => {
|
|
95
|
+
if (placementFrame.current !== undefined) return
|
|
96
|
+
placementFrame.current = requestAnimationFrame(() => {
|
|
97
|
+
placementFrame.current = undefined
|
|
98
|
+
positionPopover()
|
|
99
|
+
})
|
|
100
|
+
}, [positionPopover])
|
|
101
|
+
const showPopover = React.useCallback(() => {
|
|
102
|
+
cancelOpen()
|
|
103
|
+
cancelClose()
|
|
104
|
+
const popover = popoverRef.current
|
|
105
|
+
if (popover === null) return
|
|
106
|
+
popover.style.visibility = 'hidden'
|
|
107
|
+
if (!replPopoverIsOpen(popover)) {
|
|
108
|
+
if (typeof popover.showPopover === 'function') {
|
|
109
|
+
try {
|
|
110
|
+
popover.showPopover()
|
|
111
|
+
} catch {
|
|
112
|
+
popover.dataset.open = 'true'
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
popover.dataset.open = 'true'
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
placeReplPopover(triggerRef.current, popover)
|
|
119
|
+
popover.style.visibility = 'visible'
|
|
120
|
+
setExpanded(true)
|
|
121
|
+
void refreshGlobalBindings()
|
|
122
|
+
}, [cancelClose, cancelOpen, refreshGlobalBindings])
|
|
123
|
+
const hidePopover = React.useCallback(() => {
|
|
124
|
+
cancelOpen()
|
|
125
|
+
cancelClose()
|
|
126
|
+
pinned.current = false
|
|
127
|
+
const popover = popoverRef.current
|
|
128
|
+
if (popover === null) return
|
|
129
|
+
if (popover.dataset.open === 'true') delete popover.dataset.open
|
|
130
|
+
if (typeof popover.hidePopover === 'function' && replPopoverIsOpen(popover)) {
|
|
131
|
+
try { popover.hidePopover() } catch {}
|
|
132
|
+
}
|
|
133
|
+
setExpanded(false)
|
|
134
|
+
}, [cancelClose, cancelOpen])
|
|
135
|
+
// Touch has no hover, so its tap reaches the same card through the click path.
|
|
136
|
+
const scheduleOpen = React.useCallback(() => {
|
|
137
|
+
// The pointer came back to the trigger: the card may still be open from before,
|
|
138
|
+
// with the close its own leave armed waiting to return it.
|
|
139
|
+
cancelClose()
|
|
140
|
+
if (replPopoverIsOpen(popoverRef.current)) return
|
|
141
|
+
cancelOpen()
|
|
142
|
+
openTimer.current = setTimeout(() => {
|
|
143
|
+
openTimer.current = undefined
|
|
144
|
+
// A hover that reaches this point is the pointer driving the card again, so a
|
|
145
|
+
// later leave closes it instead of inheriting the keyboard exemption.
|
|
146
|
+
keyboard.current = false
|
|
147
|
+
showPopover()
|
|
148
|
+
}, HOVER_DWELL_MS)
|
|
149
|
+
}, [cancelClose, cancelOpen, showPopover])
|
|
150
|
+
const scheduleHide = React.useCallback(() => {
|
|
151
|
+
cancelOpen()
|
|
152
|
+
cancelClose()
|
|
153
|
+
if (pinned.current) return
|
|
154
|
+
closeTimer.current = setTimeout(() => {
|
|
155
|
+
closeTimer.current = undefined
|
|
156
|
+
// A keyboard reader keeps the card: focus inside it, or on its own trigger.
|
|
157
|
+
if (popoverRef.current?.contains(document.activeElement)) return
|
|
158
|
+
if (keyboard.current && document.activeElement === triggerRef.current) return
|
|
159
|
+
hidePopover()
|
|
160
|
+
}, CLOSE_GRACE_MS)
|
|
161
|
+
}, [cancelClose, cancelOpen, hidePopover])
|
|
162
|
+
const togglePopover = React.useCallback(() => {
|
|
163
|
+
cancelOpen()
|
|
164
|
+
if (!replPopoverIsOpen(popoverRef.current)) {
|
|
165
|
+
// The click owns the card until the reader closes it explicitly.
|
|
166
|
+
pinned.current = true
|
|
167
|
+
showPopover()
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
if (!pinned.current) { pinned.current = true; return }
|
|
171
|
+
hidePopover()
|
|
172
|
+
}, [cancelOpen, hidePopover, showPopover])
|
|
173
|
+
const dismissOutside = React.useCallback(event => {
|
|
174
|
+
if (event.target instanceof Node && shellRef.current?.contains(event.target) !== true) hidePopover()
|
|
175
|
+
}, [hidePopover])
|
|
176
|
+
const closeOnEscape = React.useCallback(event => {
|
|
177
|
+
if (event.key !== 'Escape') return
|
|
178
|
+
hidePopover()
|
|
179
|
+
const trigger = triggerRef.current
|
|
180
|
+
if (!trigger?.getClientRects().length) return
|
|
181
|
+
restoringFocus.current = true
|
|
182
|
+
trigger.focus({ preventScroll: true })
|
|
183
|
+
restoringFocus.current = false
|
|
184
|
+
}, [hidePopover])
|
|
185
|
+
React.useEffect(() => {
|
|
186
|
+
if (!expanded) return undefined
|
|
187
|
+
// A manual popover keeps the browser out of dismissal, so both close paths
|
|
188
|
+
// stay owned here instead of racing the click that toggles it.
|
|
189
|
+
document.addEventListener('pointerdown', dismissOutside)
|
|
190
|
+
document.addEventListener('keydown', closeOnEscape)
|
|
191
|
+
return () => {
|
|
192
|
+
document.removeEventListener('pointerdown', dismissOutside)
|
|
193
|
+
document.removeEventListener('keydown', closeOnEscape)
|
|
194
|
+
}
|
|
195
|
+
}, [closeOnEscape, dismissOutside, expanded])
|
|
196
|
+
React.useEffect(() => {
|
|
197
|
+
const syncPopoverState = (event) => {
|
|
198
|
+
if (event.target !== popoverRef.current) return
|
|
199
|
+
const open = replPopoverIsOpen(popoverRef.current)
|
|
200
|
+
if (!open) pinned.current = false
|
|
201
|
+
setExpanded(open)
|
|
202
|
+
}
|
|
203
|
+
const onScroll = (event) => {
|
|
204
|
+
// Scrolling the card's own list cannot move it; only the page behind can.
|
|
205
|
+
if (popoverRef.current?.contains(event.target)) return
|
|
206
|
+
schedulePlacement()
|
|
207
|
+
}
|
|
208
|
+
window.addEventListener('resize', schedulePlacement)
|
|
209
|
+
document.addEventListener('scroll', onScroll, true)
|
|
210
|
+
document.addEventListener('toggle', syncPopoverState, true)
|
|
211
|
+
return () => {
|
|
212
|
+
cancelOpen()
|
|
213
|
+
cancelClose()
|
|
214
|
+
if (placementFrame.current !== undefined) {
|
|
215
|
+
cancelAnimationFrame(placementFrame.current)
|
|
216
|
+
placementFrame.current = undefined
|
|
217
|
+
}
|
|
218
|
+
window.removeEventListener('resize', schedulePlacement)
|
|
219
|
+
document.removeEventListener('scroll', onScroll, true)
|
|
220
|
+
document.removeEventListener('toggle', syncPopoverState, true)
|
|
221
|
+
const popover = popoverRef.current
|
|
222
|
+
if (popover?.dataset?.open === 'true') delete popover.dataset.open
|
|
223
|
+
if (typeof popover?.hidePopover === 'function' && replPopoverIsOpen(popover)) {
|
|
224
|
+
try { popover.hidePopover() } catch {}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}, [cancelClose, cancelOpen, schedulePlacement])
|
|
228
|
+
if (!sessionUsesPtcPreset(preset) || !featureEnabled(settings, 'plugin')) return null
|
|
229
|
+
let memory
|
|
230
|
+
try {
|
|
231
|
+
memory = normalizeReplMemorySnapshot(projectionMemory)
|
|
232
|
+
} catch {
|
|
233
|
+
memory = unavailableReplMemorySnapshot()
|
|
234
|
+
}
|
|
235
|
+
const popoverId = `ptc-plus-repl-${String(resolvedSessionId).replace(/[^A-Za-z0-9_-]/g, '-')}`
|
|
236
|
+
const titleId = `${popoverId}-title`
|
|
237
|
+
return h('span', { className: 'ptcPlusActiveShell', ref: shellRef },
|
|
238
|
+
h('button', {
|
|
239
|
+
type: 'button', className: 'ptcPlusActive', ref: triggerRef,
|
|
240
|
+
title: t('indicator.title'),
|
|
241
|
+
'aria-label': t('indicator.title'), 'aria-controls': popoverId,
|
|
242
|
+
'aria-expanded': expanded, 'aria-haspopup': 'dialog',
|
|
243
|
+
onPointerEnter: event => {
|
|
244
|
+
if (event.pointerType === 'touch') return
|
|
245
|
+
scheduleOpen()
|
|
246
|
+
},
|
|
247
|
+
onPointerLeave: scheduleHide,
|
|
248
|
+
// Only a keyboard reader opens the card by focusing the trigger; the focus
|
|
249
|
+
// a click leaves behind must not reopen what the click just toggled.
|
|
250
|
+
onFocus: () => { if (keyboard.current && !restoringFocus.current) showPopover() },
|
|
251
|
+
onBlur: scheduleHide, onClick: togglePopover,
|
|
252
|
+
onKeyDown: event => { if (event.key === 'Escape') hidePopover() },
|
|
253
|
+
}, h('span', { className: 'ptcPlusActiveLabel' }, 'PTC Plus')),
|
|
254
|
+
h(ReplMemoryCard, {
|
|
255
|
+
memory,
|
|
256
|
+
globalEnabled,
|
|
257
|
+
globalBindings,
|
|
258
|
+
loadGlobalBinding: id => callUserBindings('load', { id }),
|
|
259
|
+
t, id: popoverId, titleId, popoverRef,
|
|
260
|
+
onEnter: () => { cancelOpen(); cancelClose() },
|
|
261
|
+
onLeave: scheduleHide,
|
|
262
|
+
onGlobalTab: refreshGlobalBindings,
|
|
263
|
+
}))
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return { PTCPlusSessionIndicator }
|
|
267
|
+
}
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* REPL surface views: the conversation tab (session bindings + global workbench)
|
|
3
|
+
* and the header memory card. Every piece here is a pure projection of props,
|
|
4
|
+
* session projections and settings; the entry owns registration and transport.
|
|
5
|
+
*/
|
|
6
|
+
export function createReplView(React, deps) {
|
|
7
|
+
const {
|
|
8
|
+
CodeBlock, featureEnabled, normalizeReplMemorySnapshot,
|
|
9
|
+
unavailableReplMemorySnapshot, useSessionPreset, sessionUsesPtcPreset,
|
|
10
|
+
useWorkbenchController, UserBindingsWorkbench,
|
|
11
|
+
icons: { search: IconSearchOutline16, chevron: IconChevronDownOutline14 },
|
|
12
|
+
} = deps
|
|
13
|
+
const h = React.createElement
|
|
14
|
+
|
|
15
|
+
function ReplComposer() { return null }
|
|
16
|
+
|
|
17
|
+
function ReplBindingInspector({ entry, preview, t }) {
|
|
18
|
+
const readable = preview?.status === 'readable'
|
|
19
|
+
return h('aside', { className: 'ptcPlusBindingInspector', 'aria-label': entry.name },
|
|
20
|
+
h('header', { className: 'ptcPlusInspectorHead' },
|
|
21
|
+
h('strong', null, entry.name),
|
|
22
|
+
h('span', null, t('memory.location', entry.definition))),
|
|
23
|
+
h('div', { className: 'ptcPlusObservationLabel' }, t('console.definition')),
|
|
24
|
+
typeof CodeBlock === 'function'
|
|
25
|
+
? h(CodeBlock, { code: entry.definition.source, lang: 'typescript', className: 'ptcPlusObservationCode',
|
|
26
|
+
copyLabel: t('tool.copy'), copiedLabel: t('tool.copied') })
|
|
27
|
+
: h('pre', { className: 'ptcPlusObservationCode' }, entry.definition.source),
|
|
28
|
+
h('div', { className: 'ptcPlusObservationLabel' }, t('console.value'),
|
|
29
|
+
preview?.truncated ? h('span', { className: 'ptcPlusObservationState' }, t('console.bounded')) : null),
|
|
30
|
+
readable ? h('pre', { className: 'ptcPlusObservationPreview' }, preview.text)
|
|
31
|
+
: h('p', { className: 'ptcPlusObservationUnavailable' }, t(preview === undefined ? 'console.unobservedValue' : 'console.unreadable')))
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function ReplSessionBindings({ memory, t }) {
|
|
35
|
+
const [query, setQuery] = React.useState('')
|
|
36
|
+
const [kind, setKind] = React.useState('all')
|
|
37
|
+
const [selectedName, setSelectedName] = React.useState(null)
|
|
38
|
+
const entries = memory.entries.filter(entry => (kind === 'all' || entry.kind === kind)
|
|
39
|
+
&& entry.name.toLowerCase().includes(query.trim().toLowerCase()))
|
|
40
|
+
const selected = entries.find(entry => entry.name === selectedName) ?? entries[0]
|
|
41
|
+
const observation = memory.observation
|
|
42
|
+
const previews = new Map(observation?.entries.map(entry => [entry.name, entry]) ?? [])
|
|
43
|
+
return h('section', { className: 'ptcPlusConsoleSection ptcPlusSessionBindings', 'aria-label': t('console.session') },
|
|
44
|
+
h('div', { className: 'ptcPlusObservationHead' },
|
|
45
|
+
h('div', { className: 'ptcPlusObservationTitle' },
|
|
46
|
+
h('h2', null, t('console.session')),
|
|
47
|
+
memory.available ? h('span', { className: 'ptcPlusObservationCount', title: t('memory.count', { count: memory.total }) },
|
|
48
|
+
query.trim() || kind !== 'all' ? `${entries.length} / ${memory.total}` : memory.total) : null,
|
|
49
|
+
memory.available ? h('span', { className: 'ptcPlusObservationReuseTotal', title: t('memory.reuseHint') },
|
|
50
|
+
t('memory.reuseTotal', { count: memory.reuseTotal })) : null),
|
|
51
|
+
h('span', { className: 'ptcPlusObservationTime' }, observation === undefined
|
|
52
|
+
? t('console.unobserved') : t('console.observed', { time: new Date(observation.at).toLocaleString() }))),
|
|
53
|
+
memory.entries.length === 0 ? h('div', { className: 'ptcPlusSessionEmpty' },
|
|
54
|
+
t(memory.available ? 'memory.empty' : 'memory.unavailable'))
|
|
55
|
+
: h('div', { className: 'ptcPlusObservationGrid', 'data-empty': selected === undefined },
|
|
56
|
+
h('div', { className: 'ptcPlusObservationCatalog' },
|
|
57
|
+
h('div', { className: 'ptcPlusObservationFilters' },
|
|
58
|
+
h('label', { className: 'ptcPlusSearch' },
|
|
59
|
+
typeof IconSearchOutline16 === 'function' ? h(IconSearchOutline16, { size: 16 }) : null,
|
|
60
|
+
h('input', { value: query, onChange: event => setQuery(event.target.value),
|
|
61
|
+
placeholder: t('console.search'), 'aria-label': t('console.search') })),
|
|
62
|
+
h('select', { className: 'ptcPlusSelect', value: kind, onChange: event => setKind(event.target.value),
|
|
63
|
+
'aria-label': t('console.kind') },
|
|
64
|
+
['all', 'variable', 'function', 'class', 'import'].map(value => h('option', { key: value, value },
|
|
65
|
+
t(value === 'all' ? 'console.allKinds' : `memory.kind.${value}`))))),
|
|
66
|
+
h('div', { className: 'ptcPlusObservations' },
|
|
67
|
+
h('table', { className: 'ptcPlusObservationTable', 'aria-label': t('console.session') },
|
|
68
|
+
h('thead', null, h('tr', null,
|
|
69
|
+
h('th', { scope: 'col' }, t('console.name')),
|
|
70
|
+
h('th', { scope: 'col' }, t('console.kind')),
|
|
71
|
+
h('th', { scope: 'col' }, t('console.reuse')),
|
|
72
|
+
h('th', { scope: 'col' }, t('console.value')))),
|
|
73
|
+
h('tbody', null, entries.map(entry => {
|
|
74
|
+
const preview = previews.get(entry.name)
|
|
75
|
+
return h('tr', { key: entry.name, 'data-selected': entry === selected,
|
|
76
|
+
onClick: () => setSelectedName(entry.name) },
|
|
77
|
+
h('td', null, h('button', { type: 'button', className: 'ptcPlusObservationSelect',
|
|
78
|
+
'aria-current': entry === selected ? 'true' : undefined, title: entry.name }, entry.name)),
|
|
79
|
+
h('td', { className: 'ptcPlusObservationKind' }, t(`memory.kind.${entry.kind}`)),
|
|
80
|
+
h('td', { className: 'ptcPlusObservationReuse', title: t('memory.reuseHint') }, entry.reuseCount),
|
|
81
|
+
h('td', null, h('span', { className: 'ptcPlusObservationValue' },
|
|
82
|
+
preview?.status === 'readable' ? h('code', null, preview.text)
|
|
83
|
+
: h('span', { className: 'ptcPlusObservationState' }, t(preview === undefined ? 'console.unobservedValue' : 'console.unreadable')),
|
|
84
|
+
preview?.truncated ? h('span', { className: 'ptcPlusObservationState' }, t('console.bounded')) : null)))
|
|
85
|
+
}))),
|
|
86
|
+
entries.length === 0 ? h('p', { className: 'ptcPlusMessage ptcPlusObservationEmpty' },
|
|
87
|
+
t(!memory.available ? 'memory.unavailable' : memory.entries.length === 0 ? 'memory.empty' : 'console.noMatches')) : null)),
|
|
88
|
+
selected ? h(ReplBindingInspector, { entry: selected, preview: previews.get(selected.name), t }) : null),
|
|
89
|
+
memory.omitted > 0 ? h('p', { className: 'ptcPlusMessage' }, t('memory.more', { count: memory.omitted })) : null)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function ReplConsole({ t, sessionId, useProjection, useSessions, usePtcSettings, callUserBindings, hideComposer, observeRepl }) {
|
|
93
|
+
const preset = useSessionPreset({ sessionId, useProjection, useSessions })
|
|
94
|
+
const projected = useProjection('ptcPlusRepl')
|
|
95
|
+
const settings = usePtcSettings(snapshot => snapshot)
|
|
96
|
+
const globalEnabled = featureEnabled(settings, 'bindings')
|
|
97
|
+
const workbench = useWorkbenchController({ enabled: globalEnabled, active: true, callUserBindings })
|
|
98
|
+
const eligible = featureEnabled(settings, 'replView') && sessionUsesPtcPreset(preset)
|
|
99
|
+
const memory = React.useMemo(() => {
|
|
100
|
+
try { return normalizeReplMemorySnapshot(projected) } catch { return unavailableReplMemorySnapshot() }
|
|
101
|
+
}, [projected])
|
|
102
|
+
const [observed, setObserved] = React.useState(null)
|
|
103
|
+
const observationRegion = React.useRef(null)
|
|
104
|
+
React.useEffect(() => eligible ? hideComposer(sessionId) : undefined, [eligible, hideComposer, sessionId])
|
|
105
|
+
React.useEffect(() => eligible ? observeRepl(sessionId, observationRegion.current, memory,
|
|
106
|
+
value => setObserved({ sessionId, source: memory, value })) : undefined, [eligible, observeRepl, sessionId, memory])
|
|
107
|
+
if (!eligible) return null
|
|
108
|
+
return h('div', { className: 'ptcPlusConsole', 'data-conversation-composer-overlay': '' },
|
|
109
|
+
h('div', { className: 'ptcPlusConsoleSection', ref: observationRegion }, h(ReplSessionBindings, {
|
|
110
|
+
key: sessionId, memory: observed?.sessionId === sessionId && observed.source === memory ? observed.value : memory, t })),
|
|
111
|
+
globalEnabled ? h('div', { className: 'ptcPlusConsoleSection ptcPlusBindingsSurface' },
|
|
112
|
+
h(UserBindingsWorkbench, { controller: workbench, t, headingLabel: t('console.global') })) : null)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function replPopoverIsOpen(popover) {
|
|
116
|
+
if (popover?.dataset?.open === 'true') return true
|
|
117
|
+
try {
|
|
118
|
+
return popover?.matches?.(':popover-open') === true
|
|
119
|
+
} catch {
|
|
120
|
+
return false
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function placeReplPopover(trigger, popover) {
|
|
125
|
+
if (trigger === null || popover === null) return
|
|
126
|
+
const margin = 12
|
|
127
|
+
const gap = 8
|
|
128
|
+
const viewportWidth = document.documentElement.clientWidth || window.innerWidth
|
|
129
|
+
const viewportHeight = document.documentElement.clientHeight || window.innerHeight
|
|
130
|
+
const triggerRect = trigger.getBoundingClientRect()
|
|
131
|
+
const width = Math.min(344, Math.max(0, viewportWidth - margin * 2))
|
|
132
|
+
const left = Math.min(
|
|
133
|
+
Math.max(margin, triggerRect.right - width),
|
|
134
|
+
Math.max(margin, viewportWidth - width - margin),
|
|
135
|
+
)
|
|
136
|
+
const below = Math.max(0, viewportHeight - triggerRect.bottom - gap - margin)
|
|
137
|
+
const above = Math.max(0, triggerRect.top - gap - margin)
|
|
138
|
+
const opensAbove = below < 260 && above > below
|
|
139
|
+
const availableHeight = Math.max(80, opensAbove ? above : below)
|
|
140
|
+
popover.style.width = `${width}px`
|
|
141
|
+
popover.style.maxHeight = `${availableHeight}px`
|
|
142
|
+
popover.style.left = `${left}px`
|
|
143
|
+
popover.style.top = opensAbove
|
|
144
|
+
? `${Math.max(margin, triggerRect.top - gap - Math.min(popover.offsetHeight, availableHeight))}px`
|
|
145
|
+
: `${Math.min(viewportHeight - margin, triggerRect.bottom + gap)}px`
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function ReplMemoryCard({
|
|
149
|
+
memory,
|
|
150
|
+
globalEnabled,
|
|
151
|
+
globalBindings,
|
|
152
|
+
loadGlobalBinding,
|
|
153
|
+
t,
|
|
154
|
+
id,
|
|
155
|
+
titleId,
|
|
156
|
+
popoverRef,
|
|
157
|
+
onEnter,
|
|
158
|
+
onLeave,
|
|
159
|
+
onGlobalTab,
|
|
160
|
+
}) {
|
|
161
|
+
const [expandedBinding, setExpandedBinding] = React.useState(null)
|
|
162
|
+
const [tab, setTab] = React.useState('session')
|
|
163
|
+
const [globalSource, setGlobalSource] = React.useState(null)
|
|
164
|
+
const activeTab = globalEnabled ? tab : 'session'
|
|
165
|
+
const summary = activeTab === 'session'
|
|
166
|
+
? memory.available ? `${t('memory.count', { count: memory.total })} · ${t('memory.reuseTotal', { count: memory.reuseTotal })}` : ''
|
|
167
|
+
: globalBindings === undefined ? '' : t('memory.globalCount', { count: globalBindings.entries.length })
|
|
168
|
+
const inspectGlobal = entry => {
|
|
169
|
+
if (globalSource?.id === entry.id) {
|
|
170
|
+
setGlobalSource(null)
|
|
171
|
+
return
|
|
172
|
+
}
|
|
173
|
+
setGlobalSource({ id: entry.id, source: '' })
|
|
174
|
+
loadGlobalBinding(entry.id).then(
|
|
175
|
+
loaded => setGlobalSource(current => current?.id === entry.id
|
|
176
|
+
? { id: entry.id, source: loaded.entry.source }
|
|
177
|
+
: current),
|
|
178
|
+
() => setGlobalSource(current => current?.id === entry.id
|
|
179
|
+
? { id: entry.id, error: true, source: '' }
|
|
180
|
+
: current),
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
return h('div', {
|
|
184
|
+
// A manual popover owns its lifetime: the trigger toggles it, and the entry
|
|
185
|
+
// that opened it closes it on an outside press or Escape.
|
|
186
|
+
className: 'ptcPlusReplPopover', id, ref: popoverRef, popover: 'manual',
|
|
187
|
+
role: 'dialog', 'aria-labelledby': titleId,
|
|
188
|
+
onPointerEnter: onEnter, onPointerLeave: onLeave,
|
|
189
|
+
},
|
|
190
|
+
h('div', { className: 'ptcPlusReplCard' },
|
|
191
|
+
h('div', { className: 'ptcPlusReplHead' },
|
|
192
|
+
h('span', { className: 'ptcPlusReplStatusDot', 'aria-hidden': true }),
|
|
193
|
+
h('span', { className: 'ptcPlusReplTitle', id: titleId }, t('memory.title')),
|
|
194
|
+
h('span', {
|
|
195
|
+
className: 'ptcPlusReplSummary',
|
|
196
|
+
'aria-hidden': summary === '' ? true : undefined,
|
|
197
|
+
}, summary)),
|
|
198
|
+
globalEnabled
|
|
199
|
+
? h('div', { className: 'ptcPlusReplTabs', role: 'tablist' },
|
|
200
|
+
h('button', {
|
|
201
|
+
type: 'button', role: 'tab', className: 'ptcPlusReplTab',
|
|
202
|
+
'aria-selected': activeTab === 'session', onClick: () => setTab('session'),
|
|
203
|
+
}, t('memory.sessionTab')),
|
|
204
|
+
h('button', {
|
|
205
|
+
type: 'button', role: 'tab', className: 'ptcPlusReplTab',
|
|
206
|
+
'aria-selected': activeTab === 'global',
|
|
207
|
+
onClick: () => { setTab('global'); onGlobalTab?.() },
|
|
208
|
+
}, t('memory.globalTab')))
|
|
209
|
+
: null,
|
|
210
|
+
activeTab === 'global'
|
|
211
|
+
? h('div', { className: 'ptcPlusGlobalPane' },
|
|
212
|
+
globalBindings === undefined
|
|
213
|
+
? h('span', { className: 'ptcPlusReplEmpty' }, t('memory.globalUnavailable'))
|
|
214
|
+
: globalBindings.entries.length === 0
|
|
215
|
+
? h('span', { className: 'ptcPlusReplEmpty' }, t('memory.globalEmpty'))
|
|
216
|
+
: h('ul', { className: 'ptcPlusGlobalList' }, globalBindings.entries.map(entry => (
|
|
217
|
+
h('li', { key: entry.id, className: 'ptcPlusGlobalItem' },
|
|
218
|
+
h('button', {
|
|
219
|
+
type: 'button', className: 'ptcPlusBindingSelect',
|
|
220
|
+
'aria-expanded': globalSource?.id === entry.id,
|
|
221
|
+
onClick: () => inspectGlobal(entry),
|
|
222
|
+
},
|
|
223
|
+
h('span', { className: 'ptcPlusBindingName', title: entry.name }, entry.name),
|
|
224
|
+
h('span', { className: 'ptcPlusBindingMeta', title: entry.symbols.join(', ') }, `${entry.scope} - ${entry.symbols.join(', ')}`),
|
|
225
|
+
h('span', { className: 'ptcPlusBindingState', 'data-enabled': entry.enabled },
|
|
226
|
+
t(entry.enabled ? 'bindings.enabled' : 'bindings.disabledEntry'))),
|
|
227
|
+
globalSource?.id !== entry.id
|
|
228
|
+
? null
|
|
229
|
+
: globalSource.error === true
|
|
230
|
+
? h('span', { className: 'ptcPlusReplEmpty' }, t('memory.globalUnavailable'))
|
|
231
|
+
: h('pre', { className: 'ptcPlusGlobalSource' }, globalSource.source))
|
|
232
|
+
)))
|
|
233
|
+
)
|
|
234
|
+
: !memory.available
|
|
235
|
+
? h('span', { className: 'ptcPlusReplEmpty' }, t('memory.unavailable'))
|
|
236
|
+
: memory.entries.length === 0
|
|
237
|
+
? h('span', { className: 'ptcPlusReplEmpty' }, t('memory.empty'))
|
|
238
|
+
: h('ul', { className: 'ptcPlusReplList' }, memory.entries.map((binding, index) => {
|
|
239
|
+
const expanded = expandedBinding === binding.name
|
|
240
|
+
const preview = binding.definition.source.replace(/\s+/g, ' ').trim()
|
|
241
|
+
const definitionId = `${id}-binding-${index}`
|
|
242
|
+
const toggle = () => setExpandedBinding(current => (
|
|
243
|
+
current === binding.name ? null : binding.name
|
|
244
|
+
))
|
|
245
|
+
return h('li', {
|
|
246
|
+
className: 'ptcPlusReplBinding', key: binding.name,
|
|
247
|
+
'data-expanded': expanded,
|
|
248
|
+
},
|
|
249
|
+
h('button', {
|
|
250
|
+
className: 'ptcPlusReplBindingTrigger', type: 'button',
|
|
251
|
+
'aria-expanded': expanded,
|
|
252
|
+
'aria-controls': expanded ? definitionId : undefined,
|
|
253
|
+
onClick: toggle,
|
|
254
|
+
},
|
|
255
|
+
h('span', { className: 'ptcPlusReplIdentity' },
|
|
256
|
+
h('span', {
|
|
257
|
+
className: 'ptcPlusReplName', 'data-kind': binding.kind,
|
|
258
|
+
title: `${binding.name} - ${t(`memory.kind.${binding.kind}`)}`,
|
|
259
|
+
}, binding.name),
|
|
260
|
+
h('span', { className: 'ptcPlusReplReuse', title: t('memory.reuseHint') },
|
|
261
|
+
t('memory.reuseCount', { count: binding.reuseCount }))),
|
|
262
|
+
h('span', { className: 'ptcPlusReplPreview', title: preview }, preview),
|
|
263
|
+
h('span', {
|
|
264
|
+
className: 'ptcPlusReplChevron', 'data-open': expanded, 'aria-hidden': true,
|
|
265
|
+
}, h(IconChevronDownOutline14, { size: 14 }))),
|
|
266
|
+
expanded
|
|
267
|
+
? h('div', {
|
|
268
|
+
className: 'ptcPlusReplDefinitionWrap', 'data-open': true,
|
|
269
|
+
id: definitionId, role: 'region', 'aria-label': binding.name,
|
|
270
|
+
}, h('div', { className: 'ptcPlusReplDefinitionInner' },
|
|
271
|
+
h('div', { className: 'ptcPlusReplDefinition' },
|
|
272
|
+
h('span', { className: 'ptcPlusReplLocation' }, t('memory.location', {
|
|
273
|
+
line: binding.definition.line,
|
|
274
|
+
column: binding.definition.column,
|
|
275
|
+
})),
|
|
276
|
+
typeof CodeBlock === 'function'
|
|
277
|
+
? h(CodeBlock, {
|
|
278
|
+
code: binding.definition.source,
|
|
279
|
+
lang: 'typescript',
|
|
280
|
+
className: 'ptcPlusReplCode',
|
|
281
|
+
copyLabel: t('tool.copy'),
|
|
282
|
+
copiedLabel: t('tool.copied'),
|
|
283
|
+
})
|
|
284
|
+
: h('pre', { className: 'ptcPlusReplCode' }, binding.definition.source))))
|
|
285
|
+
: null)
|
|
286
|
+
})),
|
|
287
|
+
tab !== 'session' || memory.omitted === 0 ? null
|
|
288
|
+
: h('span', { className: 'ptcPlusReplMore' }, t('memory.more', { count: memory.omitted }))))
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return { ReplComposer, ReplConsole, ReplMemoryCard, replPopoverIsOpen, placeReplPopover }
|
|
292
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { RPC_CONTRACTS, RPC_REMOTE } from '../internal/rpc-contract.js'
|
|
2
|
+
|
|
3
|
+
/** Mount and consume the shared contract through the Host's public Remote caller. */
|
|
4
|
+
export async function createClientRpc(ctx) {
|
|
5
|
+
await ctx.remote.$mount(RPC_REMOTE)
|
|
6
|
+
const services = Object.values(RPC_CONTRACTS).map(({ service }) => `remote.${service}`)
|
|
7
|
+
let scope
|
|
8
|
+
await ctx.inject(services, current => {
|
|
9
|
+
scope = current
|
|
10
|
+
current.effect(() => () => { if (scope === current) scope = undefined })
|
|
11
|
+
})
|
|
12
|
+
return {
|
|
13
|
+
async call(contract, operation, payload, signal) {
|
|
14
|
+
if (scope === undefined) throw new Error('PTC Plus Remote service is unavailable')
|
|
15
|
+
const args = signal === undefined ? [operation, payload] : [operation, payload, signal]
|
|
16
|
+
const result = await scope.remote[contract.service].invoke(...args)
|
|
17
|
+
return result.ok ? result.value : result
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
}
|