dsh-ptc-plus 0.4.10 → 0.5.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 (122) hide show
  1. package/README.en.md +60 -130
  2. package/README.md +60 -130
  3. package/assets/dsh-ptc-plus-banner-en.webp +0 -0
  4. package/assets/dsh-ptc-plus-banner-zh.webp +0 -0
  5. package/assets/ptc-plus-repl-workspace-en.png +0 -0
  6. package/assets/ptc-plus-repl-workspace-zh.png +0 -0
  7. package/assets/ptc-plus-settings-en.png +0 -0
  8. package/assets/ptc-plus-settings-zh.png +0 -0
  9. package/client.cjs +42336 -0
  10. package/cordis.patch.yml +1 -0
  11. package/docs/README.md +6 -2
  12. package/docs/adr/0005-temporary-rejected-cell-edit-transport.md +31 -198
  13. package/docs/adr/0008-define-agent-operating-contract.md +5 -1
  14. package/docs/adr/0009-bound-expensive-acceptance-default.md +2 -2
  15. package/docs/adr/0016-archive-resolved-review-findings.md +1 -1
  16. package/docs/adr/0017-track-the-latest-dsh-public-surface.md +2 -2
  17. package/docs/adr/0019-plugin-settings-and-kill-switch.md +1 -1
  18. package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +1 -1
  19. package/docs/adr/0022-stage-npm-releases-with-oidc.md +4 -2
  20. package/docs/adr/0023-global-user-bindings.md +2 -0
  21. package/docs/adr/0026-persist-the-delivery-verdict-against-the-verified-candidate.md +2 -0
  22. package/docs/adr/0028-attach-to-the-host-ptc-execution-seam.md +25 -23
  23. package/docs/adr/0031-close-semantic-obligations-by-owner.md +1 -1
  24. package/docs/adr/0034-keep-program-values-separate-from-output-projections.md +29 -0
  25. package/docs/adr/0035-run-coverage-only-before-a-new-release.md +27 -0
  26. package/docs/adr/0036-separate-focused-development-from-delivery-verification.md +29 -0
  27. package/docs/adr/0037-keep-tool-hover-previews-passive.md +52 -0
  28. package/docs/adr/0038-freeze-session-capability-plans.md +71 -0
  29. package/docs/adr/0039-verify-documentation-without-runtime-suites.md +29 -0
  30. package/docs/adr/0040-separate-capability-configurations-from-session-observations.md +39 -0
  31. package/docs/adr/0041-record-cell-execution-directories.md +65 -0
  32. package/docs/adr/0042-persist-provider-availability-and-handback.md +41 -0
  33. package/docs/adr/0043-compose-capability-and-context-policies.md +37 -0
  34. package/docs/adr/README.md +6 -0
  35. package/docs/architecture.md +23 -25
  36. package/docs/capability-planning-components.md +93 -0
  37. package/docs/capability-projection.md +7 -0
  38. package/docs/client-ui.md +70 -23
  39. package/docs/client-visual-contract.md +26 -9
  40. package/docs/context-management.md +22 -0
  41. package/docs/context-optimization-plan.md +353 -0
  42. package/docs/context-public-interface-coverage.md +25 -0
  43. package/docs/durability-design.md +20 -15
  44. package/docs/evaluation.md +41 -6
  45. package/docs/evaluations/command-goat-deepseek-v4.1-flash-20261006.md +126 -0
  46. package/docs/evaluations/command-goat-deepseek-v4.1-flash.md +281 -0
  47. package/docs/installation.md +31 -10
  48. package/docs/program-data-plane.md +5 -3
  49. package/docs/publishing.md +5 -5
  50. package/docs/release-coverage.md +73 -0
  51. package/docs/release-notes.md +1 -1
  52. package/docs/review-procedure.md +56 -0
  53. package/docs/runtime-reference.md +48 -16
  54. package/docs/semantic-validation.md +14 -2
  55. package/docs/stateful-binding-updates-proposal.md +2 -2
  56. package/docs/user-bindings.md +1 -1
  57. package/docs/value-wire.md +11 -1
  58. package/docs/verification-optimization-work.md +26 -15
  59. package/docs/verification.md +30 -88
  60. package/index.js +123 -20
  61. package/internal/binding-toggle-receipt.js +24 -0
  62. package/internal/capability-command-receipt.js +29 -0
  63. package/internal/capability-environment.js +1247 -0
  64. package/internal/capability-plan.js +285 -0
  65. package/internal/capability-policy.js +96 -0
  66. package/internal/capability-statistics-store.js +180 -0
  67. package/internal/capability-statistics.js +210 -0
  68. package/internal/config-spec.js +79 -9
  69. package/internal/direct-surface-owner.js +27 -29
  70. package/internal/dynamic-environment-runtime.js +9 -9
  71. package/internal/edit-transport-owner.js +180 -139
  72. package/internal/execution-provider-owner.js +15 -12
  73. package/internal/execution-seam-compat.js +8 -8
  74. package/internal/kernel-worker.js +104 -32
  75. package/internal/native-root-dynamic.js +12 -13
  76. package/internal/program-bindings.js +190 -0
  77. package/internal/recovery-tips.js +3 -2
  78. package/internal/rejected-cell-editor.js +8 -8
  79. package/internal/repl-observation-interest.js +11 -3
  80. package/internal/rpc-contract.js +1 -0
  81. package/internal/runtime-bridge-owner.js +272 -34
  82. package/internal/runtime-config.js +58 -0
  83. package/internal/runtime-contexts.js +74 -5
  84. package/internal/runtime-intrinsics.js +1 -0
  85. package/internal/runtime-messages.js +41 -0
  86. package/internal/sdk-projection.js +10 -82
  87. package/internal/session-cell-executor.js +42 -19
  88. package/internal/session-events.js +34 -0
  89. package/internal/session-journal-recovery.js +234 -11
  90. package/internal/session-journal-schema.js +6 -3
  91. package/internal/session-journal.js +31 -2
  92. package/internal/session-log-view.js +1 -1
  93. package/internal/session-runtime.js +119 -15
  94. package/internal/settings-compat.js +12 -5
  95. package/internal/stateful-module-compiler.js +3 -2
  96. package/internal/stateful-module-runtime.js +7 -3
  97. package/internal/stateful-root-runtime.js +9 -4
  98. package/internal/user-binding-authoring.js +2 -7
  99. package/internal/user-bindings-owner.js +41 -2
  100. package/internal/validated-parse-repair.js +1 -1
  101. package/internal/value-wire.js +68 -21
  102. package/internal/worker-cwd-virtualization.js +20 -18
  103. package/internal/worker-realm-surfaces.js +3 -3
  104. package/package.json +47 -29
  105. package/src/client-authoring-view.js +67 -30
  106. package/src/client-binding-review.js +2 -1
  107. package/src/client-binding-toggle.js +18 -0
  108. package/src/client-capabilities-view.js +1341 -0
  109. package/src/client-capability-sort.js +29 -0
  110. package/src/client-copy.js +871 -8
  111. package/src/client-feature-gates.js +3 -0
  112. package/src/client-indicator-view.js +4 -6
  113. package/src/client-primitives.js +33 -2
  114. package/src/client-repl-view.js +20 -13
  115. package/src/client-settings-view.js +411 -41
  116. package/src/client-styles.js +108 -15
  117. package/src/client-tool-hover.js +185 -0
  118. package/src/client-tool-view.js +37 -8
  119. package/src/client-transport.js +44 -9
  120. package/src/client-workbench.js +6 -7
  121. package/src/client.js +76 -22
  122. package/client.js +0 -32479
@@ -1,5 +1,6 @@
1
1
  import { bindingDraftProjection, bindingReviewStatus } from './client-binding-review.js'
2
2
  import { bindingModelPreferences } from '../internal/user-binding-model-context.js'
3
+ import { capabilityCommandText } from './client-capabilities-view.js'
3
4
  import { featureEnabled } from './client-feature-gates.js'
4
5
  import {
5
6
  MENU_CHILDREN_PROBE_SENTINEL,
@@ -25,7 +26,7 @@ export function createAuthoringView(React, deps) {
25
26
  const {
26
27
  ActionButton, IconButton, Menu, MenuFallback, Toast, Tooltip, CodeBlock, Modal, createPortal, BindingsDialog, PTCPlusSettingsDialog,
27
28
  useWorkbenchController, useBindingReview, catalogOwner, callUserBindings, subscribeReset,
28
- settingsCardSeat, updateSetting, menuChildren,
29
+ settingsCardSeat, updateSetting, updateCapabilityConfiguration, menuChildren, SessionCapabilityControls,
29
30
  icons: {
30
31
  sparkle: IconSparkle16, chevron: IconChevronDownOutline14,
31
32
  close: IconCloseOutline16, check: IconCheckOutline14,
@@ -64,10 +65,11 @@ export function createAuthoringView(React, deps) {
64
65
  }[settingsCardSeat?.()] ?? 'settings.menuEntry')
65
66
 
66
67
  function BindingAuthorButton({
67
- sessionId, t, useInput, inputActions, usePtcSettings, useBindingCommand,
68
+ sessionId, t, useInput, inputActions, usePtcSettings, useBindingCommand, useCapabilityCommand,
68
69
  }) {
69
70
  const input = useInput?.(snapshot => snapshot)
70
71
  const available = useBindingCommand(snapshot => snapshot)
72
+ const capabilityAvailable = useCapabilityCommand?.(snapshot => snapshot) === true
71
73
  const settings = usePtcSettings(snapshot => snapshot)
72
74
  const [review, view] = useBindingReview(sessionId)
73
75
  const anchorRef = React.useRef(null)
@@ -84,6 +86,7 @@ export function createAuthoringView(React, deps) {
84
86
  const hoverTimer = React.useRef(undefined)
85
87
  const [menu, setMenu] = React.useState(null)
86
88
  const [managing, setManaging] = React.useState(false)
89
+ const [capabilitiesOpen, setCapabilitiesOpen] = React.useState(false)
87
90
  const [settingsOpen, setSettingsOpen] = React.useState(false)
88
91
  // The composer entry's four actions live in the host Menu's `children`
89
92
  // region. Preceding Clients accept that prop and mount nothing, so the entry
@@ -105,16 +108,25 @@ export function createAuthoringView(React, deps) {
105
108
  const catalogFocus = React.useRef(null)
106
109
  const toastSequence = React.useRef(0)
107
110
  const [toast, setToast] = React.useState(null)
108
- const hasDraft = view.mounted && view.candidate !== null && view.action === null
111
+ const hasDraft = featureEnabled(settings, 'bindings') && view.mounted && view.candidate !== null && view.action === null
109
112
  const quickAccess = featureEnabled(settings, 'authorButton')
110
- const canAuthor = quickAccess && available && typeof useInput === 'function' && typeof inputActions?.setDraft === 'function'
111
- const menuOpen = (hasDraft || quickAccess) && view.reachable && !managing && !settingsOpen
113
+ const pluginEnabled = featureEnabled(settings, 'plugin') && (hasDraft || featureEnabled(settings, 'ptcMenu'))
114
+ const canPrefill = typeof useInput === 'function' && typeof inputActions?.setDraft === 'function'
115
+ const canAuthor = quickAccess && available && canPrefill
116
+ const canRefresh = pluginEnabled && settings.status === 'ready' && settings.writable === true
117
+ && capabilityAvailable && canPrefill
118
+ const menuOpen = pluginEnabled && view.reachable && !managing && !capabilitiesOpen && !settingsOpen
112
119
  && menu !== null && menu.key === view.candidateKey
113
120
  // The dialog owns the management session; losing the input surface only
114
121
  // suspends reads, so a draft survives a temporary Host takeover.
115
122
  const workbench = useWorkbenchController({
116
123
  enabled: quickAccess && managing, active: view.reachable, callUserBindings,
117
124
  })
125
+ React.useEffect(() => {
126
+ if (!quickAccess) {
127
+ setManaging(false)
128
+ }
129
+ }, [quickAccess])
118
130
  const refreshCatalog = React.useCallback(async (reload = false) => {
119
131
  if (reload) captureCatalogFocus()
120
132
  await catalogSource.read({ reload })
@@ -124,6 +136,7 @@ export function createAuthoringView(React, deps) {
124
136
  catalogSource.reset()
125
137
  setMenu(null)
126
138
  setManaging(false)
139
+ setCapabilitiesOpen(false)
127
140
  setSettingsOpen(false)
128
141
  }
129
142
  const unsubscribe = subscribeReset(reset)
@@ -135,9 +148,14 @@ export function createAuthoringView(React, deps) {
135
148
  const toggleBinding = async entry => {
136
149
  if (!catalogReady || !quickAccess) return
137
150
  captureCatalogFocus()
138
- await catalogSource.write(() => callUserBindings(entry.enabled ? 'disable' : 'enable', {
151
+ const next = await catalogSource.write(() => callUserBindings(entry.enabled ? 'disable' : 'enable', {
139
152
  id: entry.id, expectedRevision: catalog.revision,
140
153
  }))
154
+ const saved = next?.entries.find(item => item.id === entry.id)
155
+ if (saved) {
156
+ toastSequence.current++
157
+ setToast({ sequence: toastSequence.current, key: saved.enabled ? 'bindings.toggleEnabled' : 'bindings.toggleDisabled', params: { name: saved.name } })
158
+ }
141
159
  }
142
160
  const cancelHoverOpen = React.useCallback(() => {
143
161
  if (hoverTimer.current === undefined) return
@@ -260,13 +278,12 @@ export function createAuthoringView(React, deps) {
260
278
  return () => clearTimeout(timer)
261
279
  }, [toast])
262
280
  const label = hasDraft ? t('bindings.reviewMenuLabel', { count: 1 })
263
- + (view.message ? ` · ${t('bindings.reviewAttention')}` : '') : t('bindings.open')
264
- const hint = t(hasDraft ? 'bindings.draftHint' : 'bindings.openHint')
265
- // Every authoring action goes through the same busy guard and input owner, so a
266
- // new or revision request can never overwrite an unsent user draft.
267
- const prefillAuthoring = value => {
281
+ + (view.message ? ` · ${t('bindings.reviewAttention')}` : '') : t(quickAccess ? 'bindings.open' : 'settings.toolsMenu')
282
+ const hint = t(hasDraft ? 'bindings.draftHint' : quickAccess ? 'bindings.openHint' : 'settings.toolsMenu')
283
+ // Command shortcuts share the composer guard; submission remains the user's action.
284
+ const prefillCommand = (value, allowed) => {
268
285
  hideMenu()
269
- if (!canAuthor) return
286
+ if (!allowed) return
270
287
  if (typeof input?.draft === 'string' && input.draft.trim() !== '') {
271
288
  toastSequence.current += 1
272
289
  setToast({ sequence: toastSequence.current, text: t('bindings.composerBusy') })
@@ -274,8 +291,8 @@ export function createAuthoringView(React, deps) {
274
291
  }
275
292
  inputActions.setDraft(value)
276
293
  }
277
- const openAuthoring = () => prefillAuthoring('/binding new ')
278
- const openAuthoringEdit = entry => prefillAuthoring(`/binding edit ${entry.id} `)
294
+ const openAuthoring = () => prefillCommand('/binding new ', canAuthor)
295
+ const openAuthoringEdit = entry => prefillCommand(`/binding edit ${entry.id} `, canAuthor)
279
296
  const starButton = h('button', {
280
297
  type: 'button', className: 'ptcPlusAuthorButton', 'aria-label': label,
281
298
  // Older UI-kit lines ship no Tooltip primitive; the native title carries the hint there.
@@ -309,7 +326,6 @@ export function createAuthoringView(React, deps) {
309
326
  const enabledEntries = (catalog?.entries ?? []).filter(entry => entry.enabled)
310
327
  const disabledEntries = (catalog?.entries ?? []).filter(entry => !entry.enabled)
311
328
  const catalogItems = !quickAccess ? [] : [
312
- { id: 'global-heading', type: 'label', text: t('bindings.quickHeading') },
313
329
  ...(catalog === null || catalog.entries.length === 0 ? [] : [
314
330
  { id: 'enabled-heading', type: 'label', text: t('bindings.quickEnabled', { count: enabledEntries.length }) },
315
331
  ...enabledEntries.map(catalogRow),
@@ -335,14 +351,7 @@ export function createAuthoringView(React, deps) {
335
351
  // submenu here would lose the bounded, scrollable catalog this step needs.
336
352
  const editItems = [
337
353
  { id: 'edit-heading', type: 'label', text: t('bindings.quickEditHeading') },
338
- ...(catalog?.entries ?? []).map(entry => ({
339
- id: `edit:${entry.id}`,
340
- disabled: catalog === null,
341
- label: h('span', { className: 'ptcPlusBindingQuickRow' },
342
- h('span', { className: 'ptcPlusBindingQuickName' }, h('strong', { title: entry.name }, entry.name),
343
- h('span', { className: 'ptcPlusBindingQuickState' }, t(entry.enabled ? 'bindings.enabled' : 'bindings.disabledEntry'))),
344
- h('span', { className: 'ptcPlusBindingQuickPurpose', title: entry.purpose }, entry.purpose)),
345
- })),
354
+ ...(catalog?.entries ?? []).map(entry => ({ ...catalogRow(entry), id: `edit:${entry.id}` })),
346
355
  ]
347
356
  const selectMenuItem = id => {
348
357
  if (!anchorRef.current?.getClientRects().length) return
@@ -371,6 +380,8 @@ export function createAuthoringView(React, deps) {
371
380
  setSettingsOpen(true)
372
381
  return
373
382
  }
383
+ if (id === 'capabilities') { hideMenu(); setCapabilitiesOpen(true); return }
384
+ if (id === 'refresh') { prefillCommand('/capabilities refresh', canRefresh); return }
374
385
  hideMenu()
375
386
  if (id === 'new') openAuthoring()
376
387
  else if (id === 'manage') {
@@ -381,12 +392,27 @@ export function createAuthoringView(React, deps) {
381
392
  }
382
393
  // One copy of each action label serves the grid the plugin renders itself,
383
394
  // in whichever carrier the installed Menu publishes for it.
384
- const actionCopy = (id, copy) => h('span', {
395
+ const actionPaths = {
396
+ new: 'M8 3v10M3 8h10',
397
+ edit: 'm10 3 3 3M3 13l3-.7 7-7a1.4 1.4 0 0 0-3-3l-7 7L3 13Z',
398
+ refresh: 'M13 6a5 5 0 0 0-9-2L2 6m0-3v3h3M3 10a5 5 0 0 0 9 2l2-2m0 3v-3h-3',
399
+ manage: 'M2 4h5l1.5 2H14v7H2V4Z',
400
+ capabilities: 'M3 4h10M3 8h10M3 12h10M6 2v4M10 6v4M6 10v4',
401
+ settings: 'M6 2h4l.5 2 1.5 1.5 2 .5v4l-2 .5-1.5 1.5-.5 2H6l-.5-2L4 10.5 2 10V6l2-.5L5.5 4 6 2ZM10 8a2 2 0 1 0-4 0 2 2 0 0 0 4 0Z',
402
+ 'edit-back': 'm7 3-5 5 5 5M2 8h12',
403
+ }
404
+ const actionCopy = (id, copy) => h(React.Fragment, null,
405
+ h('svg', { className: 'ptcPlusMenuActionIcon', width: 16, height: 16, viewBox: '0 0 16 16',
406
+ fill: 'none', 'aria-hidden': true }, h('path', { d: actionPaths[id], stroke: 'currentColor',
407
+ strokeWidth: 1.4, strokeLinecap: 'round', strokeLinejoin: 'round' })), h('span', {
385
408
  className: 'ptcPlusBindingMenuAction',
386
409
  title: id === 'settings' ? settingsPath(t) : undefined,
387
- }, t(copy))
410
+ }, t(copy)))
388
411
  const actionButton = (id, copy) => h('button', {
389
412
  type: 'button', role: 'menuitem', className: 'ptcPlusMenuButton',
413
+ disabled: id === 'refresh' && !canRefresh,
414
+ title: id === 'refresh' && !capabilityAvailable ? t('capabilities.refreshUnavailable')
415
+ : id === 'settings' ? settingsPath(t) : t(copy),
390
416
  onClick: () => selectMenuItem(id),
391
417
  }, actionCopy(id, copy))
392
418
  const actionSections = [
@@ -396,7 +422,9 @@ export function createAuthoringView(React, deps) {
396
422
  actionButton('new', 'bindings.authorNewDraft'),
397
423
  (catalog?.entries?.length ?? 0) > 0 ? actionButton('edit', 'bindings.authorEdit') : null)) : null,
398
424
  h('div', { key: 'utilities', className: 'ptcPlusMenuUtilities' },
425
+ actionButton('refresh', 'capabilities.applySession'),
399
426
  quickAccess ? actionButton('manage', 'bindings.manage') : null,
427
+ SessionCapabilityControls ? actionButton('capabilities', 'capabilities.quick.title') : null,
400
428
  actionButton('settings', 'settings.menuEntry')),
401
429
  ]
402
430
  const menuEntries = menu?.step === 'edit' ? editItems : [
@@ -421,7 +449,8 @@ export function createAuthoringView(React, deps) {
421
449
  : entry.type === 'label'
422
450
  ? h('div', { key: entry.id, className: 'ptcPlusMenuGroupLabel' }, entry.text)
423
451
  : h('button', {
424
- key: entry.id, type: 'button', role: 'menuitem', className: 'ptcPlusOwnedMenuRow',
452
+ key: entry.id, type: 'button', role: 'menuitem',
453
+ className: entry.id === 'disabled-group' ? 'ptcPlusOwnedMenuRow ptcPlusMenuDisclosure' : 'ptcPlusOwnedMenuRow',
425
454
  disabled: entry.disabled, 'data-binding-id': entry.bindingId,
426
455
  ref: entry.id === 'reload' ? reloadItemRef : entry.id === 'disabled-group' ? disabledGroupRef : undefined,
427
456
  'aria-expanded': entry.id === 'disabled-group' ? disabledOpen : undefined,
@@ -435,7 +464,7 @@ export function createAuthoringView(React, deps) {
435
464
  'data-text': isHostIconComponent(IconSparkle16) ? undefined : true,
436
465
  ref: attachAnchor,
437
466
  },
438
- !hasDraft && !quickAccess ? null : h(menuChildrenRegion ? Menu : MenuFallback, {
467
+ !pluginEnabled ? null : h(menuChildrenRegion ? Menu : MenuFallback, {
439
468
  className: 'ptcPlusAuthorButtonShell', open: menuOpen,
440
469
  anchor: isHostComponent(Tooltip)
441
470
  ? h(Tooltip, { label: hint, delayMs: 700, disabled: menuOpen || managing || settingsOpen }, starButton) : starButton,
@@ -465,9 +494,17 @@ export function createAuthoringView(React, deps) {
465
494
  controller: workbench, t, onClose: () => setManaging(false), returnFocusRef: dialogReturnFocus,
466
495
  getReturnFocus: () => anchorRef.current?.querySelector('.ptcPlusAuthorButton'),
467
496
  }) : null,
497
+ SessionCapabilityControls && capabilitiesOpen && view.reachable
498
+ ? h(SessionCapabilityControls, { sessionId, open: true, t,
499
+ enabled: pluginEnabled && settings.status === 'ready' && settings.writable === true,
500
+ onClose: () => {
501
+ setCapabilitiesOpen(false)
502
+ queueMicrotask(() => anchorRef.current?.querySelector('.ptcPlusAuthorButton')
503
+ ?.focus({ preventScroll: true }))
504
+ } }) : null,
468
505
  settingsOpen && view.reachable
469
506
  ? h(PTCPlusSettingsDialog, {
470
- t, usePtcSettings, updateSetting, callUserBindings,
507
+ t, usePtcSettings, updateSetting, updateCapabilityConfiguration, callUserBindings,
471
508
  onClose: () => {
472
509
  setSettingsOpen(false)
473
510
  queueMicrotask(() => anchorRef.current?.querySelector('.ptcPlusAuthorButton')
@@ -477,13 +514,13 @@ export function createAuthoringView(React, deps) {
477
514
  toast === null ? null : isHostComponent(Toast)
478
515
  ? h(Toast, {
479
516
  key: toast.sequence,
480
- text: toast.text,
517
+ text: toast.key ? t(toast.key, toast.params) : capabilityCommandText(t, toast.text),
481
518
  anchor: anchorRef.current,
482
519
  onDone: () => setToast(null),
483
520
  })
484
521
  : h('span', {
485
522
  key: toast.sequence, className: 'ptcPlusComposerNotice', role: 'status',
486
- }, toast.text))
523
+ }, toast.key ? t(toast.key, toast.params) : capabilityCommandText(t, toast.text)))
487
524
  }
488
525
 
489
526
  function BindingCandidateContent({ candidate, t, showName = true }) {
@@ -202,7 +202,8 @@ export function createBindingReviews(call) {
202
202
  return () => {
203
203
  attached = false
204
204
  invalidate()
205
- publish({ mounted: false, reachable: false, writable: false })
205
+ // The composer observer owns reachability independently of draft mounting.
206
+ publish({ mounted: false, writable: false })
206
207
  }
207
208
  },
208
209
  reset() {
@@ -0,0 +1,18 @@
1
+ import { readBindingToggle } from '../internal/binding-toggle-receipt.js'
2
+
3
+ /** A separate Chat node keeps configuration receipts distinct from ordinary hidden contexts. */
4
+ export const bindingToggleConversationDefinition = Object.freeze({
5
+ kind: 'ptc-plus-binding-toggle', target: 'chat',
6
+ match(event) {
7
+ return event.type === 'user/message' && event.surfaceOp === 'append' && readBindingToggle(event.data)
8
+ ? { id: event.data.id, role: 'start' } : null
9
+ },
10
+ start(_context, match) { return { seq: match.event.seq, receipt: readBindingToggle(match.event.data) } },
11
+ update(context) { return context.state },
12
+ buildViewNode(context) {
13
+ if (!context.state?.receipt) return null
14
+ return { key: context.key, id: context.id, kind: 'ptc-plus-binding-toggle', target: 'chat',
15
+ anchorSeq: context.state.seq, location: context.start?.location ?? { kind: 'unresolved' },
16
+ visibility: 'visible', data: context.state.receipt }
17
+ },
18
+ })