dsh-ptc-plus 0.4.2 → 0.4.3
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 +166 -76
- package/README.md +167 -77
- package/assets/dsh-ptc-plus-banner-en.webp +0 -0
- package/assets/dsh-ptc-plus-banner-zh.webp +0 -0
- package/assets/ptc-plus-icon.svg +5 -0
- package/client.js +593 -306
- package/compiler-core.cjs +162 -160
- package/compiler-core.cjs.map +3 -3
- package/docs/README.md +3 -1
- package/docs/adr/0005-temporary-rejected-cell-edit-transport.md +8 -7
- package/docs/adr/0008-define-agent-operating-contract.md +2 -2
- package/docs/adr/0010-session-log-derived-recovery-tips.md +7 -0
- package/docs/adr/0016-archive-resolved-review-findings.md +3 -3
- package/docs/adr/0019-plugin-settings-and-kill-switch.md +3 -3
- package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +2 -0
- package/docs/adr/0021-separate-host-bootstrap-from-session-runtime.md +14 -3
- package/docs/adr/0023-global-user-bindings.md +1 -1
- package/docs/adr/0026-persist-the-delivery-verdict-against-the-verified-candidate.md +23 -7
- package/docs/adr/0027-run-the-session-kernel-in-a-killable-helper-process.md +4 -0
- package/docs/adr/0028-attach-to-the-host-ptc-execution-seam.md +26 -6
- package/docs/adr/0031-close-semantic-obligations-by-owner.md +29 -0
- package/docs/adr/README.md +4 -4
- package/docs/architecture.md +6 -5
- package/docs/binding-review-placement-design.md +1 -1
- package/docs/client-ui.md +8 -4
- package/docs/durability-design.md +7 -7
- package/docs/installation.md +26 -8
- package/docs/runtime-reference.md +18 -11
- package/docs/semantic-validation.md +7 -3
- package/docs/user-bindings.en.md +7 -6
- package/docs/user-bindings.md +6 -5
- package/docs/value-wire.md +1 -1
- package/docs/verification.md +38 -1
- package/index.js +39 -34
- package/internal/async-local-storage-intrinsics.js +12 -7
- package/internal/callable-source-facts.js +37 -1
- package/internal/cell-analysis.js +64 -9
- package/internal/config-spec.js +1 -1
- package/internal/cordis-tools-owner.js +20 -10
- package/internal/edit-transport-owner.js +80 -20
- package/internal/execution-seam-compat.js +192 -30
- package/internal/kernel-worker.js +162 -72
- package/internal/message-sources.js +14 -0
- package/internal/module-compilation.js +2 -1
- package/internal/repl-memory-projection.js +10 -0
- package/internal/repl-scope-normalizer.js +72 -35
- package/internal/repl-value-observer.js +141 -45
- package/internal/runtime-config.js +62 -2
- package/internal/runtime-intrinsics.js +3 -0
- package/internal/runtime-messages.js +6 -4
- package/internal/session-cell-executor.js +24 -1
- package/internal/session-journal-recovery.js +267 -67
- package/internal/session-journal.js +466 -40
- package/internal/session-log-view.js +5 -4
- package/internal/session-runtime.js +45 -21
- package/internal/session-state.js +7 -29
- package/internal/settings-compat.js +66 -1
- package/internal/source-position-map.js +19 -0
- package/internal/user-binding-console-worker.js +23 -9
- package/internal/user-binding-draft-projection.js +3 -2
- package/internal/user-bindings-owner.js +2 -1
- package/internal/user-bindings.js +6 -1
- package/internal/validated-parse-repair.js +6 -2
- package/internal/value-wire.js +95 -30
- package/internal/worker-client.js +99 -8
- package/internal/worker-cwd-virtualization.js +10 -9
- package/internal/worker-output-capture.js +187 -0
- package/internal/worker-output-fence.js +26 -0
- package/internal/worker-realm-surfaces.js +197 -0
- package/internal/worker-repl-realm.js +10 -4
- package/package.json +36 -4
- package/src/client-activity.js +4 -1
- package/src/client-authoring-view.js +121 -62
- package/src/client-console.js +4 -2
- package/src/client-copy.js +4 -2
- package/src/client-feature-gates.js +7 -9
- package/src/client-host-compat.js +104 -27
- package/src/client-repl-view.js +3 -1
- package/src/client-styles.js +12 -1
- package/src/client-tool-view.js +3 -1
- package/src/client-transport.js +2 -2
- package/src/client-workbench.js +4 -3
- package/src/client.js +56 -17
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { bindingDraftProjection, bindingReviewStatus } from './client-binding-review.js'
|
|
2
2
|
import { bindingModelPreferences } from '../internal/user-binding-model-context.js'
|
|
3
3
|
import { featureEnabled } from './client-feature-gates.js'
|
|
4
|
+
import {
|
|
5
|
+
MENU_CHILDREN_PROBE_SENTINEL,
|
|
6
|
+
isHostIconComponent,
|
|
7
|
+
readMenuChildrenProbe,
|
|
8
|
+
} from './client-host-compat.js'
|
|
4
9
|
|
|
5
10
|
/**
|
|
6
11
|
* Hover dwell before the composer entry opens its menu. The entry sits inside the
|
|
@@ -19,7 +24,7 @@ export function createAuthoringView(React, deps) {
|
|
|
19
24
|
const {
|
|
20
25
|
ActionButton, IconButton, Menu, Toast, Tooltip, CodeBlock, BindingsDialog, PTCPlusSettingsDialog,
|
|
21
26
|
useWorkbenchController, useBindingReview, catalogOwner, callUserBindings, subscribeReset,
|
|
22
|
-
settingsCardSeat, updateSetting,
|
|
27
|
+
settingsCardSeat, updateSetting, menuChildren,
|
|
23
28
|
icons: {
|
|
24
29
|
sparkle: IconSparkle16, chevron: IconChevronDownOutline14,
|
|
25
30
|
close: IconCloseOutline16, check: IconCheckOutline14,
|
|
@@ -31,12 +36,13 @@ export function createAuthoringView(React, deps) {
|
|
|
31
36
|
* Where the settings card lives on the installed generation.
|
|
32
37
|
*
|
|
33
38
|
* The compat module reports the seat the host declared rather than the
|
|
34
|
-
* generation, so this copy names the place a user can open now.
|
|
35
|
-
* seat
|
|
36
|
-
* offers; the preceding generation's own seat replaces it once it registers.
|
|
39
|
+
* generation, so this copy names the place a user can open now. An undeclared
|
|
40
|
+
* seat carries no location evidence and keeps the generic settings label.
|
|
37
41
|
*/
|
|
38
|
-
const settingsPath = t => t(
|
|
39
|
-
|
|
42
|
+
const settingsPath = t => t({
|
|
43
|
+
'settings.plugin.item': 'settings.pathSettings',
|
|
44
|
+
'plugins.row.config': 'settings.pathPlugins',
|
|
45
|
+
}[settingsCardSeat?.()] ?? 'settings.menuEntry')
|
|
40
46
|
|
|
41
47
|
function BindingAuthorButton({
|
|
42
48
|
sessionId, t, useInput, inputActions, usePtcSettings, useBindingCommand,
|
|
@@ -60,6 +66,13 @@ export function createAuthoringView(React, deps) {
|
|
|
60
66
|
const [menu, setMenu] = React.useState(null)
|
|
61
67
|
const [managing, setManaging] = React.useState(false)
|
|
62
68
|
const [settingsOpen, setSettingsOpen] = React.useState(false)
|
|
69
|
+
// The composer entry's four actions live in the host Menu's `children`
|
|
70
|
+
// region. Preceding Clients accept that prop and mount nothing, so the entry
|
|
71
|
+
// probes the installed primitive once and, when it renders no such region,
|
|
72
|
+
// keeps the same actions reachable through the Menu's pinned rows.
|
|
73
|
+
const probeRef = React.useRef(null)
|
|
74
|
+
const [probing, setProbing] = React.useState(() => menuChildren.supported() === undefined)
|
|
75
|
+
const menuChildrenRegion = menuChildren.supported() !== false
|
|
63
76
|
// The menu catalog and the workbench catalog are independent sources: a
|
|
64
77
|
// read or write in one never invalidates the other's in-flight request.
|
|
65
78
|
const catalogSource = React.useMemo(() => catalogOwner.claim(), [catalogOwner])
|
|
@@ -193,6 +206,16 @@ export function createAuthoringView(React, deps) {
|
|
|
193
206
|
})
|
|
194
207
|
return () => { cancelled = true }
|
|
195
208
|
}, [menuOpen, menu?.mode, menu?.step])
|
|
209
|
+
React.useLayoutEffect(() => {
|
|
210
|
+
// The probe mounts in the same commit as this effect, so the answer comes
|
|
211
|
+
// from Client output rather than from the props the plugin passed. A probe
|
|
212
|
+
// that never mounted records nothing: the entry keeps rendering the region
|
|
213
|
+
// instead of turning a missing container into a compatibility verdict.
|
|
214
|
+
if (!probing) return
|
|
215
|
+
const mounted = probeRef.current
|
|
216
|
+
if (mounted !== null) menuChildren.record(readMenuChildrenProbe(mounted))
|
|
217
|
+
setProbing(false)
|
|
218
|
+
}, [probing, menuChildren])
|
|
196
219
|
React.useEffect(() => {
|
|
197
220
|
if (toast === null || typeof Toast === 'function') return undefined
|
|
198
221
|
const timer = setTimeout(() => setToast(null), 2_500)
|
|
@@ -218,7 +241,7 @@ export function createAuthoringView(React, deps) {
|
|
|
218
241
|
const starButton = h('button', {
|
|
219
242
|
type: 'button', className: 'ptcPlusAuthorButton', 'aria-label': label,
|
|
220
243
|
// Older UI-kit lines ship no Tooltip primitive; the native title carries the hint there.
|
|
221
|
-
title: typeof Tooltip === 'function' ? undefined : hint,
|
|
244
|
+
title: typeof Tooltip === 'function' || menuOpen || managing || settingsOpen ? undefined : hint,
|
|
222
245
|
'aria-haspopup': 'menu', 'aria-expanded': menuOpen,
|
|
223
246
|
onPointerEnter: hoverMenu,
|
|
224
247
|
onPointerLeave: cancelHoverOpen,
|
|
@@ -229,15 +252,19 @@ export function createAuthoringView(React, deps) {
|
|
|
229
252
|
if (menuOpen && menu.mode !== 'hover') hideMenu()
|
|
230
253
|
else showMenu('click')
|
|
231
254
|
},
|
|
232
|
-
},
|
|
255
|
+
}, isHostIconComponent(IconSparkle16)
|
|
233
256
|
? h(IconSparkle16, { size: 16, 'aria-hidden': true })
|
|
234
257
|
: h('span', { className: 'ptcPlusAuthorButtonLabel', 'aria-hidden': true }, t('bindings.open')),
|
|
235
258
|
hasDraft ? h('span', { className: 'ptcPlusDraftBadge', 'aria-hidden': true,
|
|
236
259
|
'data-attention': view.message !== null }, '1') : null)
|
|
260
|
+
// A write in flight keeps the list interactive. The source serializes writes
|
|
261
|
+
// and drops a duplicate, while disabling rows would dim every row and add a
|
|
262
|
+
// pending row for the duration of the request: both read as the popover
|
|
263
|
+
// flickering when the user toggles an entry.
|
|
237
264
|
const catalogItems = !quickAccess ? [] : [
|
|
238
265
|
{ id: 'global-heading', type: 'label', text: t('bindings.quickHeading') },
|
|
239
266
|
...(catalog?.entries ?? []).map(entry => ({ id: `global:${entry.id}`,
|
|
240
|
-
disabled:
|
|
267
|
+
disabled: catalog === null,
|
|
241
268
|
label: h('span', { className: 'ptcPlusBindingQuickRow', 'data-enabled': entry.enabled },
|
|
242
269
|
h('span', { className: 'ptcPlusBindingQuickName' }, h('strong', { title: entry.name }, entry.name),
|
|
243
270
|
h('span', { className: 'ptcPlusBindingQuickState' }, t(entry.enabled ? 'bindings.enabled' : 'bindings.disabledEntry'))),
|
|
@@ -245,7 +272,7 @@ export function createAuthoringView(React, deps) {
|
|
|
245
272
|
})),
|
|
246
273
|
...(catalog !== null && catalog.entries.length === 0
|
|
247
274
|
? [{ id: 'empty', type: 'label', text: t('memory.globalEmpty') }] : []),
|
|
248
|
-
...(
|
|
275
|
+
...(catalog === null && catalogStatus !== 'error'
|
|
249
276
|
? [{ id: 'pending', type: 'label', text: t(catalogStatus === 'writing' ? 'bindings.quickSaving' : 'bindings.quickLoading') }] : []),
|
|
250
277
|
...(catalogError === null ? [] : [{ id: 'error', type: 'label', text: t('bindings.failed', { error: catalogError }) }]),
|
|
251
278
|
]
|
|
@@ -256,7 +283,7 @@ export function createAuthoringView(React, deps) {
|
|
|
256
283
|
{ id: 'edit-heading', type: 'label', text: t('bindings.quickEditHeading') },
|
|
257
284
|
...(catalog?.entries ?? []).map(entry => ({
|
|
258
285
|
id: `edit:${entry.id}`,
|
|
259
|
-
disabled:
|
|
286
|
+
disabled: catalog === null,
|
|
260
287
|
label: h('span', { className: 'ptcPlusBindingQuickRow' },
|
|
261
288
|
h('span', { className: 'ptcPlusBindingQuickName' }, h('strong', { title: entry.name }, entry.name),
|
|
262
289
|
h('span', { className: 'ptcPlusBindingQuickState' }, t(entry.enabled ? 'bindings.enabled' : 'bindings.disabledEntry'))),
|
|
@@ -266,79 +293,111 @@ export function createAuthoringView(React, deps) {
|
|
|
266
293
|
// when the catalog is momentarily empty.
|
|
267
294
|
{ id: 'edit-back', label: h('span', { className: 'ptcPlusBindingMenuAction', ref: firstItemRef }, t('bindings.back')) },
|
|
268
295
|
]
|
|
296
|
+
const selectMenuItem = id => {
|
|
297
|
+
if (!anchorRef.current?.getClientRects().length) return
|
|
298
|
+
if (id === 'edit') {
|
|
299
|
+
setMenu(current => current === null ? current : { ...current, step: 'edit' })
|
|
300
|
+
return
|
|
301
|
+
}
|
|
302
|
+
if (id === 'edit-back') {
|
|
303
|
+
setMenu(current => current === null ? current : { ...current, step: 'root' })
|
|
304
|
+
return
|
|
305
|
+
}
|
|
306
|
+
if (id.startsWith('edit:')) {
|
|
307
|
+
const entry = catalog?.entries.find(entry => `edit:${entry.id}` === id)
|
|
308
|
+
if (entry) openAuthoringEdit(entry)
|
|
309
|
+
return
|
|
310
|
+
}
|
|
311
|
+
if (id.startsWith('global:')) {
|
|
312
|
+
const entry = catalog?.entries.find(entry => `global:${entry.id}` === id)
|
|
313
|
+
if (entry) void toggleBinding(entry)
|
|
314
|
+
return
|
|
315
|
+
}
|
|
316
|
+
if (id === 'reload') { void refreshCatalog(true); return }
|
|
317
|
+
if (id === 'settings') {
|
|
318
|
+
hideMenu()
|
|
319
|
+
setSettingsOpen(true)
|
|
320
|
+
return
|
|
321
|
+
}
|
|
322
|
+
hideMenu()
|
|
323
|
+
if (id === 'new') openAuthoring()
|
|
324
|
+
else if (id === 'manage') {
|
|
325
|
+
dialogReturnFocus.current = null
|
|
326
|
+
setManaging(true)
|
|
327
|
+
}
|
|
328
|
+
else if (id === view.candidateKey) openBindingReview(review, view.candidate)
|
|
329
|
+
}
|
|
330
|
+
// One copy of each action label serves the grid the plugin renders itself,
|
|
331
|
+
// in whichever carrier the installed Menu publishes for it.
|
|
332
|
+
const actionCopy = (id, copy, ref) => h('span', {
|
|
333
|
+
ref, className: 'ptcPlusBindingMenuAction',
|
|
334
|
+
title: id === 'settings' ? settingsPath(t) : undefined,
|
|
335
|
+
}, t(copy))
|
|
336
|
+
const actionButton = (id, copy, ref) => h('button', {
|
|
337
|
+
type: 'button', role: 'menuitem', className: 'ptcPlusMenuButton',
|
|
338
|
+
onClick: () => selectMenuItem(id),
|
|
339
|
+
}, actionCopy(id, copy, ref))
|
|
340
|
+
const actionSections = [
|
|
341
|
+
canAuthor ? h('div', { key: 'authoring', className: 'ptcPlusMenuAuthoring', role: 'group', 'aria-label': t('bindings.authorGroup') },
|
|
342
|
+
h('div', { className: 'ptcPlusMenuGroupLabel' }, t('bindings.authorGroup')),
|
|
343
|
+
h('div', { className: 'ptcPlusMenuActionGrid' },
|
|
344
|
+
actionButton('new', 'bindings.authorNewDraft'),
|
|
345
|
+
(catalog?.entries?.length ?? 0) > 0 ? actionButton('edit', 'bindings.authorEdit') : null)) : null,
|
|
346
|
+
h('div', { key: 'utilities', className: 'ptcPlusMenuUtilities' },
|
|
347
|
+
quickAccess ? actionButton('manage', 'bindings.manage', manageItemRef) : null,
|
|
348
|
+
actionButton('settings', 'settings.menuEntry', settingsItemRef)),
|
|
349
|
+
]
|
|
350
|
+
const actionsInRegion = menu?.step === 'edit'
|
|
351
|
+
? null : h('div', { className: 'ptcPlusMenuActions' }, actionSections)
|
|
352
|
+
// The plugin's own action area, never a row list. A Client that mounts no
|
|
353
|
+
// `children` region still renders the pinned entry area, which carries the
|
|
354
|
+
// same grouped grid; the published `text` type names a string while the
|
|
355
|
+
// implementation renders the value, so this stays the plugin's own markup
|
|
356
|
+
// rather than a set of host-styled rows. Neither carrier enters the draft
|
|
357
|
+
// menu's second step, which owns its own rows.
|
|
358
|
+
const actionsPinned = menuChildrenRegion || menu?.step === 'edit' ? [] : [{
|
|
359
|
+
id: 'ptc-plus-actions',
|
|
360
|
+
type: 'label',
|
|
361
|
+
text: h('div', { className: 'ptcPlusMenuActions ptcPlusMenuActionsPinned' }, actionSections),
|
|
362
|
+
}]
|
|
269
363
|
return h('span', {
|
|
270
364
|
className: 'ptcPlusComposerBindingAnchor', tabIndex: -1,
|
|
271
365
|
onFocusCapture: () => { focused.current = true }, onBlurCapture: () => { focused.current = false },
|
|
272
|
-
'data-text':
|
|
366
|
+
'data-text': isHostIconComponent(IconSparkle16) ? undefined : true,
|
|
273
367
|
ref: attachAnchor,
|
|
274
368
|
},
|
|
275
369
|
!hasDraft && !quickAccess ? null : h(Menu, {
|
|
276
370
|
className: 'ptcPlusAuthorButtonShell', open: menuOpen,
|
|
277
371
|
anchor: typeof Tooltip === 'function'
|
|
278
|
-
? h(Tooltip, { label: hint, delayMs:
|
|
372
|
+
? h(Tooltip, { label: hint, delayMs: 700, disabled: menuOpen || managing || settingsOpen }, starButton) : starButton,
|
|
279
373
|
portal: true, side: 'top', dense: true,
|
|
280
374
|
// A hovered menu leaves with the pointer once it leaves the trigger and the
|
|
281
375
|
// list for the published grace, which re-entering either cancels; click and
|
|
282
376
|
// keyboard menus stay until dismissed.
|
|
283
377
|
closeOnPointerLeave: menu !== null && menu.mode === 'hover',
|
|
284
378
|
getAnchorRect: menuAnchorRect,
|
|
285
|
-
selectedIds: (catalog?.entries ?? []).filter(entry => entry.enabled).map(entry => `global:${entry.id}`),
|
|
286
379
|
onClose: hideMenu,
|
|
287
380
|
items: menu?.step === 'edit' ? editItems : [...(hasDraft ? [{ id: 'draft-heading', type: 'label', text: t('bindings.quickDrafts') }, { id: view.candidateKey,
|
|
288
381
|
label: h('span', { className: 'ptcPlusDraftMenuItem', ref: firstItemRef },
|
|
289
382
|
h('strong', null, view.candidate.entry.name),
|
|
290
383
|
h('span', null, t(bindingReviewStatus(view)))) },
|
|
291
384
|
...(quickAccess ? [{ id: 'draft-separator', type: 'separator' }] : [])] : []), ...catalogItems,
|
|
292
|
-
...(quickAccess
|
|
293
|
-
|
|
294
|
-
...(canAuthor && (catalog?.entries?.length ?? 0) > 0
|
|
295
|
-
? [{ id: 'edit', label: h('span', { className: 'ptcPlusBindingMenuAction' }, t('bindings.authorEdit')) }] : []),
|
|
296
|
-
...(quickAccess ? [
|
|
297
|
-
...(catalogError === null ? [] : [{ id: 'reload', label: h('span', { ref: reloadItemRef }, t('bindings.reload')) }]),
|
|
298
|
-
{ id: 'manage', label: h('span', { ref: manageItemRef, className: 'ptcPlusBindingMenuAction' }, t('bindings.manage')) },
|
|
299
|
-
] : []),
|
|
300
|
-
// The entry is also the shortcut to this plugin's own settings, so the
|
|
301
|
-
// row names the seat the installed generation declared and its hint
|
|
302
|
-
// carries the full path that opens it.
|
|
303
|
-
{ id: 'settings', label: h('span', {
|
|
304
|
-
ref: settingsItemRef, className: 'ptcPlusBindingMenuAction', title: settingsPath(t),
|
|
305
|
-
}, t('settings.menuEntry')) },
|
|
385
|
+
...(quickAccess && catalogError !== null
|
|
386
|
+
? [{ id: 'reload', label: h('span', { ref: reloadItemRef }, t('bindings.reload')) }] : []),
|
|
306
387
|
],
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
setMenu(current => current === null ? current : { ...current, step: 'edit' })
|
|
311
|
-
return
|
|
312
|
-
}
|
|
313
|
-
if (id === 'edit-back') {
|
|
314
|
-
setMenu(current => current === null ? current : { ...current, step: 'root' })
|
|
315
|
-
return
|
|
316
|
-
}
|
|
317
|
-
if (id.startsWith('edit:')) {
|
|
318
|
-
const entry = catalog?.entries.find(entry => `edit:${entry.id}` === id)
|
|
319
|
-
if (entry) openAuthoringEdit(entry)
|
|
320
|
-
return
|
|
321
|
-
}
|
|
322
|
-
if (id.startsWith('global:')) {
|
|
323
|
-
const entry = catalog?.entries.find(entry => `global:${entry.id}` === id)
|
|
324
|
-
if (entry) void toggleBinding(entry)
|
|
325
|
-
return
|
|
326
|
-
}
|
|
327
|
-
if (id === 'reload') { void refreshCatalog(true); return }
|
|
328
|
-
if (id === 'settings') {
|
|
329
|
-
hideMenu()
|
|
330
|
-
setSettingsOpen(true)
|
|
331
|
-
return
|
|
332
|
-
}
|
|
333
|
-
hideMenu()
|
|
334
|
-
if (id === 'new') openAuthoring()
|
|
335
|
-
else if (id === 'manage') {
|
|
336
|
-
dialogReturnFocus.current = null
|
|
337
|
-
setManaging(true)
|
|
338
|
-
}
|
|
339
|
-
else if (id === view.candidateKey) openBindingReview(review, view.candidate)
|
|
340
|
-
},
|
|
388
|
+
children: actionsInRegion,
|
|
389
|
+
...(actionsPinned.length === 0 ? {} : { footer: actionsPinned }),
|
|
390
|
+
onSelect: selectMenuItem,
|
|
341
391
|
}),
|
|
392
|
+
// The probe mounts the host Menu's own `children` region inside a hidden
|
|
393
|
+
// non-portal list and is dropped in the same commit as the effect that
|
|
394
|
+
// reads it, so no Client ever paints it and the entry's own menu keeps the
|
|
395
|
+
// only interactive surface.
|
|
396
|
+
probing ? h('span', { className: 'ptcPlusMenuProbe', ref: probeRef, 'aria-hidden': 'true' },
|
|
397
|
+
h(Menu, {
|
|
398
|
+
open: true, items: [], anchor: h('span', null), onSelect: selectMenuItem, onClose: hideMenu,
|
|
399
|
+
children: h('span', { className: MENU_CHILDREN_PROBE_SENTINEL }),
|
|
400
|
+
})) : null,
|
|
342
401
|
// Hiding the composer unmounts the dialog, not the controller: the draft survives.
|
|
343
402
|
managing && quickAccess && view.reachable
|
|
344
403
|
? h(BindingsDialog, {
|
package/src/client-console.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { isHostIconComponent } from './client-host-compat.js'
|
|
2
|
+
|
|
1
3
|
const MAX_HISTORY_ENTRIES = 40
|
|
2
4
|
const MAX_HISTORY_CHARACTERS = 128 * 1024
|
|
3
5
|
const MAX_RECORD_CHARACTERS = MAX_HISTORY_CHARACTERS / 2
|
|
@@ -89,9 +91,9 @@ export function createBindingConsole(React, { TypeScriptEditor, IconButton, Acti
|
|
|
89
91
|
h('span', { className: 'ptcPlusExecutionState', role: 'status' }, t(`execution.${state}`)),
|
|
90
92
|
h('div', { className: 'ptcPlusExecutionActions' },
|
|
91
93
|
running
|
|
92
|
-
? h(ActionButton, { onClick: () => { release(); setState('released') } },
|
|
94
|
+
? h(ActionButton, { onClick: () => { release(); setState('released') } }, isHostIconComponent(icons.stop) ? h(icons.stop, { size: 14 }) : null, t('execution.stop'))
|
|
93
95
|
: h(ActionButton, { 'data-kind': 'primary', onClick: run, disabled: !code.trim() || !source.trim() },
|
|
94
|
-
|
|
96
|
+
isHostIconComponent(icons.play) ? h(icons.play, { size: 14 }) : null, t('bindings.run')),
|
|
95
97
|
h(IconButton, { icon: icons.reset, label: t('execution.reset'), onClick: () => { release(); setState('ready') } }),
|
|
96
98
|
h(IconButton, { icon: icons.clear, label: t('execution.clear'), onClick: () => setRecords([]) }))),
|
|
97
99
|
h('div', { className: 'ptcPlusExecutionHistory', ref: history, 'aria-label': t('execution.history') },
|
package/src/client-copy.js
CHANGED
|
@@ -103,12 +103,13 @@ const CHROME_COPY = Object.freeze({
|
|
|
103
103
|
'bindings.failed': '全局用户绑定操作失败:{error}',
|
|
104
104
|
'bindings.authorEdit': '修改绑定',
|
|
105
105
|
'bindings.open': '全局绑定',
|
|
106
|
-
'bindings.openHint': 'PTC Plus
|
|
106
|
+
'bindings.openHint': 'PTC Plus · 全局绑定与设置',
|
|
107
107
|
'bindings.draftHint': 'PTC Plus 插件 · 绑定草稿待处理,点击查看',
|
|
108
108
|
'bindings.quickHeading': '全局用户绑定 · 对所有会话生效',
|
|
109
109
|
'bindings.quickDrafts': '待处理草稿',
|
|
110
110
|
'bindings.quickLoading': '正在读取绑定…',
|
|
111
111
|
'bindings.quickSaving': '正在保存…',
|
|
112
|
+
'bindings.authorGroup': 'Agent 编写',
|
|
112
113
|
'bindings.authorNewDraft': '编写新绑定',
|
|
113
114
|
'bindings.quickEditHeading': '选择要修改的绑定',
|
|
114
115
|
'bindings.back': '返回',
|
|
@@ -273,12 +274,13 @@ const CHROME_COPY = Object.freeze({
|
|
|
273
274
|
'bindings.failed': 'Global User Binding operation failed: {error}',
|
|
274
275
|
'bindings.authorEdit': 'Revise a binding',
|
|
275
276
|
'bindings.open': 'Global bindings',
|
|
276
|
-
'bindings.openHint': 'PTC Plus
|
|
277
|
+
'bindings.openHint': 'PTC Plus · Global bindings and settings',
|
|
277
278
|
'bindings.draftHint': 'PTC Plus plugin · Binding draft pending; click to review',
|
|
278
279
|
'bindings.quickHeading': 'Global bindings · Applies to all sessions',
|
|
279
280
|
'bindings.quickDrafts': 'Pending drafts',
|
|
280
281
|
'bindings.quickLoading': 'Loading bindings…',
|
|
281
282
|
'bindings.quickSaving': 'Saving…',
|
|
283
|
+
'bindings.authorGroup': 'Author with Agent',
|
|
282
284
|
'bindings.authorNewDraft': 'Write a new binding',
|
|
283
285
|
'bindings.quickEditHeading': 'Choose a binding to revise',
|
|
284
286
|
'bindings.back': 'Back',
|
|
@@ -6,21 +6,20 @@
|
|
|
6
6
|
*/
|
|
7
7
|
const FEATURE_SETTINGS = Object.freeze({
|
|
8
8
|
// The plugin master switch plus every contribution that only needs it.
|
|
9
|
-
plugin:
|
|
9
|
+
plugin: [],
|
|
10
10
|
// Global User Bindings: the workbench, its command renderer and the author menu.
|
|
11
|
-
bindings:
|
|
11
|
+
bindings: ['userBindingsEnabled'],
|
|
12
12
|
// Enhanced run_code tool rows.
|
|
13
|
-
toolView:
|
|
13
|
+
toolView: ['enhancedToolView'],
|
|
14
14
|
// The REPL view tab; the session preset condition is added by its registration.
|
|
15
|
-
replView:
|
|
15
|
+
replView: ['replViewEnabled'],
|
|
16
16
|
// The composer authoring menu.
|
|
17
|
-
authorButton:
|
|
17
|
+
authorButton: ['userBindingsEnabled', 'bindingAuthorButtonVisible'],
|
|
18
18
|
})
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Pure settings projection: a feature is eligible only from a ready snapshot
|
|
22
|
-
* with the master switch on
|
|
23
|
-
* settings must be explicitly true; default-on settings are on unless the user
|
|
22
|
+
* with the master switch on. These features default to on unless the user
|
|
24
23
|
* turned them off. Writability gates setting writes, not contribution presence.
|
|
25
24
|
*/
|
|
26
25
|
export function featureEnabled(snapshot, feature) {
|
|
@@ -28,8 +27,7 @@ export function featureEnabled(snapshot, feature) {
|
|
|
28
27
|
if (rule === undefined) throw new Error(`Unknown client feature: ${feature}`)
|
|
29
28
|
if (snapshot?.status !== 'ready' || snapshot.value?.enabled !== true) return false
|
|
30
29
|
const value = snapshot.value
|
|
31
|
-
return rule.
|
|
32
|
-
&& rule.defaultOn.every(key => value[key] !== false)
|
|
30
|
+
return rule.every(key => value[key] !== false)
|
|
33
31
|
}
|
|
34
32
|
|
|
35
33
|
/**
|
|
@@ -1,5 +1,102 @@
|
|
|
1
1
|
import { SETTINGS_NAMESPACE } from '../internal/config-spec.js'
|
|
2
2
|
|
|
3
|
+
const HOST_ICON_EXPORTS = Object.freeze({
|
|
4
|
+
check: ['IconCheckOutlineRegular', 'IconCheckOutline14'],
|
|
5
|
+
chevron: ['IconChevronDownOutlineRegular', 'IconChevronDownOutline14'],
|
|
6
|
+
inspect: ['IconInspectOutlineRegular', 'IconInspectOutline12'],
|
|
7
|
+
sparkle: ['IconSparkleRegular', 'IconSparkle16'],
|
|
8
|
+
close: ['IconCloseOutlineRegular', 'IconCloseOutline16'],
|
|
9
|
+
search: ['IconSearchOutlineRegular', 'IconSearchOutline16'],
|
|
10
|
+
plus: ['IconPlusOutlineRegular', 'IconPlusOutline16'],
|
|
11
|
+
refresh: ['IconRefreshOutlineRegular', 'IconRefreshOutline16'],
|
|
12
|
+
trash: ['IconTrashOutlineRegular', 'IconTrashOutline16'],
|
|
13
|
+
edit: ['IconEditOutlineRegular', 'IconEditOutline16'],
|
|
14
|
+
play: ['IconPlayOutlineRegular', 'IconPlayOutline16'],
|
|
15
|
+
stop: ['IconStopFillRegular', 'IconStopFill16'],
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The one component that stands in for a glyph the host did not publish.
|
|
20
|
+
*
|
|
21
|
+
* Call sites render `icons.<key>` as a glyph, so an omitted, renamed, or
|
|
22
|
+
* malformed export must still resolve to a renderable element type. The marker
|
|
23
|
+
* stays readable so a control that can show its own text label instead
|
|
24
|
+
* (`IconButton`) keeps that fallback rather than rendering an empty button.
|
|
25
|
+
*/
|
|
26
|
+
const ABSENT_HOST_ICON = () => null
|
|
27
|
+
ABSENT_HOST_ICON.absentHostIcon = true
|
|
28
|
+
|
|
29
|
+
/** Whether a normalized icon entry is a glyph the host actually published. */
|
|
30
|
+
export function isHostIconComponent(icon) {
|
|
31
|
+
return typeof icon === 'function' && icon.absentHostIcon !== true
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Normalize the public icon export shape used by current and preceding Clients.
|
|
36
|
+
*
|
|
37
|
+
* The mapping is total: every key resolves to a render-safe component, so a
|
|
38
|
+
* host generation that renames or omits an icon never turns a missing glyph
|
|
39
|
+
* into a render-time throw of the whole plugin surface.
|
|
40
|
+
*/
|
|
41
|
+
export function hostIconComponents(primitives) {
|
|
42
|
+
return Object.fromEntries(Object.entries(HOST_ICON_EXPORTS).map(([name, candidates]) => [
|
|
43
|
+
name,
|
|
44
|
+
candidates.map(candidate => primitives?.[candidate])
|
|
45
|
+
.find(value => typeof value === 'function') ?? ABSENT_HOST_ICON,
|
|
46
|
+
]))
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The class the composer entry mounts as the probe's sentinel child.
|
|
51
|
+
*
|
|
52
|
+
* `src/client-authoring-view.js` renders it inside the host `Menu`'s own
|
|
53
|
+
* `children` region; this module owns the selector that reads it back.
|
|
54
|
+
*/
|
|
55
|
+
export const MENU_CHILDREN_PROBE_SENTINEL = 'ptcPlusMenuChildrenProbe'
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Whether the installed `Menu` primitive mounts the caller's own action area.
|
|
59
|
+
*
|
|
60
|
+
* The published `@deepseek-ai/dsh-client-ui-primitives` Menu gained its
|
|
61
|
+
* `children` region with the 0.1.7 generation. Preceding Clients accept the
|
|
62
|
+
* prop and mount nothing, so the prop name proves no more than the call the
|
|
63
|
+
* plugin already makes. The entry therefore mounts that region once inside a
|
|
64
|
+
* hidden non-portal Menu and reads what the Client actually rendered — the
|
|
65
|
+
* consumed contract is observed behavior, never a version comparison
|
|
66
|
+
* ([ADR 0017](../docs/adr/0017-track-the-latest-dsh-public-surface.md)).
|
|
67
|
+
*
|
|
68
|
+
* @param container - the probe's mounted wrapper, or nothing when it never
|
|
69
|
+
* mounted.
|
|
70
|
+
* @returns whether the sentinel is part of the mounted Client output. A
|
|
71
|
+
* container that never mounted proves no region, and its caller records that
|
|
72
|
+
* answer only for a probe that did mount.
|
|
73
|
+
*/
|
|
74
|
+
export function readMenuChildrenProbe(container) {
|
|
75
|
+
if (container === null || container === undefined) return false
|
|
76
|
+
return container.querySelector(`.${MENU_CHILDREN_PROBE_SENTINEL}`) !== null
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The probe's answer for one loaded Client.
|
|
81
|
+
*
|
|
82
|
+
* Composer entries are mounted per session, so the first observation owns the
|
|
83
|
+
* answer for the whole Client lifetime: a later mount reuses it instead of
|
|
84
|
+
* rendering a second probe, and a partially applied answer cannot contradict
|
|
85
|
+
* the rendering path already chosen. `undefined` means no probe has run yet,
|
|
86
|
+
* which callers must treat as the current rendering path, not as a verdict.
|
|
87
|
+
*
|
|
88
|
+
* @returns `supported()` reporting the recorded answer, and `record()` taking
|
|
89
|
+
* the first one. A record that throws or never runs leaves the question open
|
|
90
|
+
* and keeps the entry on the path it already renders.
|
|
91
|
+
*/
|
|
92
|
+
export function createMenuChildrenEvidence() {
|
|
93
|
+
let supported
|
|
94
|
+
return {
|
|
95
|
+
supported: () => supported,
|
|
96
|
+
record(value) { supported ??= value === true },
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
3
100
|
/** The preset a session publishes, preferring the projection reader's evidence. */
|
|
4
101
|
function sessionPresetValue(projected, summary) {
|
|
5
102
|
if (projected !== undefined) return projected
|
|
@@ -89,29 +186,13 @@ export function isIdleSessionComposer(owner, sessionId) {
|
|
|
89
186
|
const BUNDLE_PATCH_ROW_ID = 'ptc-plus'
|
|
90
187
|
|
|
91
188
|
/**
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
* `settings.plugin.item` (keyed, root) as the child slot of its configurable
|
|
97
|
-
* tab and renders one card per settings namespace the Host serves, so this
|
|
98
|
-
* plugin's key is its namespace.
|
|
99
|
-
* - DSH 0.1.6-alpha.2 and later: that section is a read-only inventory, and a
|
|
100
|
-
* bundle's own configuration belongs to the side bar Plugins page, which
|
|
101
|
-
* declares `plugins.row.config` (keyed, root) and renders the entry's summary
|
|
102
|
-
* on the row's page with the entry's form below it. The key is
|
|
103
|
-
* `<bundle package name>#<row id>`. The same page's `plugins.item` list is its
|
|
104
|
-
* Official group, occupied by the host-plane configuration pages DSH itself
|
|
105
|
-
* ships, so it is not a seat for a third-party bundle.
|
|
106
|
-
*
|
|
107
|
-
* No generation declares both keys, and `ctx.slots.inject` waits rather than
|
|
108
|
-
* failing while a key is undeclared: the callback runs only after the declaring
|
|
109
|
-
* entry mounts, and nothing is registered until then. One publication therefore
|
|
110
|
-
* serves every generation without probing a version, and no card renders twice
|
|
111
|
-
* on a generation that exists.
|
|
189
|
+
* Plugin-owned configuration seats. Current Hosts use the bundle's row page;
|
|
190
|
+
* preceding Hosts use the settings namespace card. General settings may coexist
|
|
191
|
+
* with either surface and is not a destination for this plugin's configuration.
|
|
192
|
+
* Undeclared slots stay pending until their owning page mounts.
|
|
112
193
|
*
|
|
113
194
|
* @param bundleName - this bundle's package name, as the profile installs it.
|
|
114
|
-
* @returns one `{ slot, identity }` per
|
|
195
|
+
* @returns one `{ slot, identity }` per supported plugin configuration surface.
|
|
115
196
|
*/
|
|
116
197
|
export function settingsCardSeats(bundleName) {
|
|
117
198
|
return [
|
|
@@ -121,12 +202,8 @@ export function settingsCardSeats(bundleName) {
|
|
|
121
202
|
}
|
|
122
203
|
|
|
123
204
|
/**
|
|
124
|
-
* Publish
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
* Only the declaring generation runs a seat's callback, so the card's location
|
|
128
|
-
* is observed rather than inferred: the seat that registered is where a user
|
|
129
|
-
* finds the card now, and that is what any copy pointing at it must say.
|
|
205
|
+
* Publish the settings card when a plugin configuration surface is declared.
|
|
206
|
+
* The observed seat supplies the native-location hint for the composer shortcut.
|
|
130
207
|
*
|
|
131
208
|
* @param ctx - browser plugin context carrying `slots`.
|
|
132
209
|
* @param options.bundleName - this bundle's package name, as the profile installs it.
|
package/src/client-repl-view.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { isHostIconComponent } from './client-host-compat.js'
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* REPL surface views: the conversation tab (session bindings + global workbench)
|
|
3
5
|
* and the header memory card. Every piece here is a pure projection of props,
|
|
@@ -56,7 +58,7 @@ export function createReplView(React, deps) {
|
|
|
56
58
|
h('div', { className: 'ptcPlusObservationCatalog' },
|
|
57
59
|
h('div', { className: 'ptcPlusObservationFilters' },
|
|
58
60
|
h('label', { className: 'ptcPlusSearch' },
|
|
59
|
-
|
|
61
|
+
isHostIconComponent(IconSearchOutline16) ? h(IconSearchOutline16, { size: 16 }) : null,
|
|
60
62
|
h('input', { value: query, onChange: event => setQuery(event.target.value),
|
|
61
63
|
placeholder: t('console.search'), 'aria-label': t('console.search') })),
|
|
62
64
|
h('select', { className: 'ptcPlusSelect', value: kind, onChange: event => setKind(event.target.value),
|
package/src/client-styles.js
CHANGED
|
@@ -39,7 +39,18 @@ const CLIENT_CSS = `
|
|
|
39
39
|
/* Keep the session-header action on the same compact 32px rhythm as DSH chrome. */
|
|
40
40
|
.ptcPlusActiveShell{display:inline-flex;height:28px;align-items:center;justify-content:center;line-height:0;vertical-align:middle}.ptcPlusActive{box-sizing:border-box;height:28px;justify-content:center;gap:6px;padding:0 6px;border:0;background:transparent;font-family:inherit;font-size:13px;font-weight:500;line-height:18px}.ptcPlusActive::before{width:6px;height:6px;flex:none;border-radius:50%;background:currentColor;box-shadow:0 0 0 2px color-mix(in srgb,currentColor 18%,transparent);content:''}.ptcPlusActive:hover,.ptcPlusActive[aria-expanded=true]{background:var(--dsw-alias-interactive-bg-hover,rgba(38,49,72,.06))}.ptcPlusActiveLabel{display:inline-flex;height:18px;align-items:center;line-height:18px}
|
|
41
41
|
@media(max-width:560px){.ptcPlusActiveShell{display:none}}
|
|
42
|
-
|
|
42
|
+
/* The compatibility probe never paints: it exists so the entry can read whether
|
|
43
|
+
the installed Menu mounted the region it was given. */
|
|
44
|
+
.ptcPlusMenuProbe{display:none}
|
|
45
|
+
/* The pinned carrier is a Client-owned label cell: the host already draws the
|
|
46
|
+
hairline above it, and the cell's own type must not leak into the grid. The
|
|
47
|
+
compound selector is load-bearing: the base .ptcPlusMenuActions rule is
|
|
48
|
+
declared later in this sheet, so an equal-specificity override loses and the
|
|
49
|
+
grid would draw a second hairline under the host's. */
|
|
50
|
+
.ptcPlusMenuActions.ptcPlusMenuActionsPinned{margin-top:0;border-top:0;font-size:13px;line-height:18px;color:var(--dsw-alias-label-primary)}
|
|
51
|
+
[role=menu]:has(.ptcPlusBindingMenuAction,.ptcPlusDraftMenuItem){width:min(320px,calc(100vw - 24px));min-width:0;max-height:min(440px,60dvh,var(--ptc-plus-menu-space,100dvh));border-radius:12px}.ptcPlusBindingQuickRow{display:flex;min-width:0;flex-direction:column;gap:3px;white-space:normal}.ptcPlusBindingQuickName{display:flex;align-items:center;justify-content:space-between;gap:12px;font-size:13px}.ptcPlusBindingQuickName strong{min-width:0;overflow:hidden;text-overflow:ellipsis;font-weight:500;white-space:nowrap}.ptcPlusBindingQuickState{flex:none;min-width:5em;text-align:end;font-size:11px;color:var(--dsw-alias-label-tertiary)}.ptcPlusBindingQuickRow[data-enabled=true] .ptcPlusBindingQuickState{color:var(--dsw-alias-state-success-primary)}.ptcPlusBindingQuickPurpose{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-tertiary);font-size:12px}.ptcPlusBindingMenuAction{font-size:13px}
|
|
52
|
+
.ptcPlusMenuActions{margin-top:6px;border-top:1px solid var(--dsw-alias-border-l3,rgba(0,0,0,.1))}.ptcPlusMenuAuthoring{padding:8px 4px}.ptcPlusMenuGroupLabel{padding:0 4px 6px;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px}.ptcPlusMenuActionGrid,.ptcPlusMenuUtilities{display:flex;gap:6px;min-width:0}.ptcPlusMenuUtilities{padding:6px 4px}.ptcPlusMenuAuthoring+.ptcPlusMenuUtilities{border-top:1px solid var(--dsw-alias-border-l3,rgba(0,0,0,.1))}.ptcPlusMenuButton{appearance:none;display:flex;flex:1;align-items:center;justify-content:center;min-width:0;min-height:34px;padding:6px 8px;border:1px solid var(--dsw-alias-border-l3,rgba(0,0,0,.1));border-radius:7px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;text-align:center;cursor:pointer}.ptcPlusMenuButton:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(38,49,72,.06))}.ptcPlusMenuButton:focus-visible{outline:2px solid var(--dsw-alias-interactive-primary,#4d6bfe);outline-offset:-2px}.ptcPlusMenuButton .ptcPlusBindingMenuAction{white-space:normal;overflow-wrap:anywhere;line-height:18px}.ptcPlusMenuUtilities .ptcPlusMenuButton{border-color:transparent;color:var(--dsw-alias-label-secondary)}.ptcPlusMenuUtilities .ptcPlusBindingMenuAction{font-size:12px}
|
|
53
|
+
|
|
43
54
|
`
|
|
44
55
|
|
|
45
56
|
const BINDING_WORKBENCH_CSS = `
|
package/src/client-tool-view.js
CHANGED
|
@@ -8,6 +8,8 @@ import { derivePtcToolView } from './client-activity.js'
|
|
|
8
8
|
export function createPtcToolView(React, deps) {
|
|
9
9
|
const { CodeBlock, DisclosureRow, icons } = deps
|
|
10
10
|
const h = React.createElement
|
|
11
|
+
const hasDisclosureRow = typeof DisclosureRow === 'function'
|
|
12
|
+
|| (typeof DisclosureRow === 'object' && DisclosureRow !== null)
|
|
11
13
|
|
|
12
14
|
function PTCPlusToolRow({ toolName, block, inspect, t }) {
|
|
13
15
|
const [open, setOpen] = React.useState(false)
|
|
@@ -60,7 +62,7 @@ export function createPtcToolView(React, deps) {
|
|
|
60
62
|
feature.detail === '' ? null
|
|
61
63
|
: h('span', { className: 'ptcPlusFeatureDetail', title: feature.detail }, feature.detail))))
|
|
62
64
|
const collapsedContent = h('div', { className: 'ptcPlusToolPreview' }, summaryLine, features)
|
|
63
|
-
if (
|
|
65
|
+
if (hasDisclosureRow) {
|
|
64
66
|
return h('div', { className: 'ptcPlusTool' },
|
|
65
67
|
h(DisclosureRow, {
|
|
66
68
|
icon: expandable
|
package/src/client-transport.js
CHANGED
|
@@ -116,7 +116,7 @@ export function createBindingCommandAvailability(scope) {
|
|
|
116
116
|
const epoch = ++entry.epoch
|
|
117
117
|
let available = false
|
|
118
118
|
try {
|
|
119
|
-
const result = await commandRemote?.
|
|
119
|
+
const result = await commandRemote?.list(String(sessionId))
|
|
120
120
|
available = result?.ok === true
|
|
121
121
|
&& Array.isArray(result.value)
|
|
122
122
|
&& result.value.some(command => command?.name === 'binding')
|
|
@@ -132,7 +132,7 @@ export function createBindingCommandAvailability(scope) {
|
|
|
132
132
|
void refresh(sessionId)
|
|
133
133
|
}
|
|
134
134
|
scope.inject(['remote', 'remote.commands'], commandScope => {
|
|
135
|
-
commandRemote = commandScope.remote
|
|
135
|
+
commandRemote = commandScope.get('remote.commands')
|
|
136
136
|
commandScope.effect(() => commandScope.remote.$on('commands/change', () => {
|
|
137
137
|
for (const sessionId of entries.keys()) void refresh(sessionId)
|
|
138
138
|
}))
|
package/src/client-workbench.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { blankBinding, editableBinding, bindingPayload } from './client-bindings-data.js'
|
|
2
|
+
import { isHostIconComponent } from './client-host-compat.js'
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* User Bindings workbench. The controller owns every piece of edit state in one
|
|
@@ -320,7 +321,7 @@ export function createUserBindingsWorkbench(React, deps) {
|
|
|
320
321
|
h(ActionButton, {
|
|
321
322
|
type: 'button', className: 'ptcPlusButton', 'data-kind': 'primary', disabled: !canCreate,
|
|
322
323
|
onClick: create,
|
|
323
|
-
},
|
|
324
|
+
}, isHostIconComponent(icons.plus) ? h(icons.plus, { size: 16 }) : null, t('bindings.new')))),
|
|
324
325
|
state.catalog === null
|
|
325
326
|
? h('p', {
|
|
326
327
|
// A failed read publishes its error on the shared source; without it
|
|
@@ -333,7 +334,7 @@ export function createUserBindingsWorkbench(React, deps) {
|
|
|
333
334
|
: h('div', { className: 'ptcPlusBindingsGrid' },
|
|
334
335
|
h('div', { className: 'ptcPlusBindingPane' },
|
|
335
336
|
h('label', { className: 'ptcPlusSearch' },
|
|
336
|
-
|
|
337
|
+
isHostIconComponent(icons.search) ? h(icons.search, { size: 16 }) : null,
|
|
337
338
|
h('input', { value: state.query, onChange: event => patch({ query: event.target.value }),
|
|
338
339
|
placeholder: t('bindings.search'), 'aria-label': t('bindings.search') })),
|
|
339
340
|
state.catalog.error === undefined
|
|
@@ -414,7 +415,7 @@ export function createUserBindingsWorkbench(React, deps) {
|
|
|
414
415
|
h('span', { className: 'ptcPlusSourceFilename' }, state.draft.name ? `${state.draft.name}.ts` : '')),
|
|
415
416
|
h('div', { className: 'ptcPlusSourceActions' }, state.editing ? null
|
|
416
417
|
: h(ActionButton, { disabled: state.busy, onClick: () => patch({ editing: true, sourceOpen: true }) },
|
|
417
|
-
|
|
418
|
+
isHostIconComponent(icons.edit) ? h(icons.edit, { size: 14 }) : null, t('bindings.edit'))),
|
|
418
419
|
h('div', { className: 'ptcPlusSourceBody', hidden: !state.sourceOpen },
|
|
419
420
|
state.editing
|
|
420
421
|
? h(TypeScriptEditor, { documentId: state.documentId, value: state.draft.source, disabled: state.busy,
|