dsh-thoughtdag 0.5.18 → 0.5.19

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 (21) hide show
  1. package/README.md +13 -12
  2. package/dist-app/assets/{canonical-agWec4XS.js → canonical-bSxBmsm7.js} +2 -2
  3. package/dist-app/assets/{canvas-record-C2yZbu5N.js → canvas-record-qO1KINUh.js} +1 -1
  4. package/dist-app/assets/{claude-code-session-CbBcaUqO.js → claude-code-session-DkNDRQp8.js} +1 -1
  5. package/dist-app/assets/{codex-session-wxU_-LWp.js → codex-session-B2hAZ_ax.js} +1 -1
  6. package/dist-app/assets/{dsh-session-DmOP3XKa.js → dsh-session-AQk-ryCb.js} +1 -1
  7. package/dist-app/assets/{experiment-loop-XP_t_khd.js → experiment-loop-BzzzDlbH.js} +1 -1
  8. package/dist-app/assets/{http-bridge-Cyr8eePQ.js → http-bridge-BnYAV4lB.js} +1 -1
  9. package/dist-app/assets/{index-DWanOFbr.js → index-BH9ozg79.js} +14 -14
  10. package/dist-app/assets/{index-ShRKYhWs.js → index-BmwffA_p.js} +1 -1
  11. package/dist-app/assets/{index-DFZu6h8O.js → index-YWyx765g.js} +3 -3
  12. package/dist-app/assets/{index-1Y_JzMpx.js → index-aOSCk5D-.js} +1 -1
  13. package/dist-app/assets/{pi-session-DXj3K4Qb.js → pi-session-CNcLBGJ1.js} +1 -1
  14. package/dist-app/assets/{sensitive-scan-B3jSpKVr.js → sensitive-scan-Cs2TuwIB.js} +1 -1
  15. package/dist-app/assets/{session-handoff-y5-kuOb1.js → session-handoff-CLQ0ZIYD.js} +2 -2
  16. package/dist-app/assets/{shared-DxaHN0fp.js → shared-LHIg0YHa.js} +1 -1
  17. package/dist-app/assets/{turndown-plugin-gfm.cjs-wNy9Tytn.js → turndown-plugin-gfm.cjs-j992pbuV.js} +1 -1
  18. package/dist-app/assets/{update-check-DyyT5hjM.js → update-check-3zMP9HL5.js} +1 -1
  19. package/dist-app/index.html +1 -1
  20. package/lib/client.js +161 -67
  21. package/package.json +1 -1
package/lib/client.js CHANGED
@@ -1,28 +1,45 @@
1
1
  // dsh-thoughtdag client half — the lightest possible browser shim.
2
- // It renders a floating "对话 | 思维图" switch over the harness UI and, on
3
- // "思维图", shows a full-screen SAME-ORIGIN iframe at /thoughtdag/ (the SPA
4
- // is served by the host half on the same web server — no CORS, no second
5
- // origin). The switch floats on purpose: it is there before any session is
6
- // open (0.4.18 moved it into the session header, which hid it on the empty
7
- // page; withdrawn in 0.4.19). While the canvas is up, the SPA shows the same
8
- // switch in its own top bar, next to the canvas chip, so nothing floats over
9
- // the canvas toolbar at any width: the pill hides once the SPA has booted
10
- // and comes back when the canvas closes (0.4.20). All conversation smarts live inside the ThoughtDAG app; this file
11
- // only opens the door and forwards the current session id so the canvas can
12
- // offer to mirror it.
2
+ // It puts ThoughtDAG where the harness puts its own pages: an entry in the
3
+ // sidebar's panel list (beside Plugins and Scheduled tasks) that opens the
4
+ // canvas in the main column, through the host's own `main` panel slot and
5
+ // `layout.selectPanel`. The canvas itself is a SAME-ORIGIN iframe at
6
+ // /thoughtdag/ (the SPA is served by the host half on the same web server —
7
+ // no CORS, no second origin). The iframe never lives inside the host's React
8
+ // tree: the main slot renders only the selected panel and would unmount it on
9
+ // every switch, reloading the canvas. So the panel page is a placeholder that
10
+ // fills the main column, and the iframe is a fixed layer laid exactly over
11
+ // it while the page is mounted — the host owns navigation, chrome, locale and
12
+ // theme; the canvas keeps its state across switches.
13
13
  //
14
- // Same pattern as dsh-synapse: window.__ModuleLoader__.load with a module
15
- // whose inject lists the client services it reads (sessions) and whose apply
16
- // touches the DOM.
14
+ // Hosts without the panel slot (older harnesses) get the previous door: a
15
+ // floating 对话 | 思维图 pill over the chat and a full-window overlay (0.4.19
16
+ // to 0.5.18; its placement was the subject of #55, #61, #63 and #73 — every
17
+ // spot in a stranger's chrome collides with something, which is why the
18
+ // panel entry replaces it wherever the host offers one).
19
+ //
20
+ // All conversation smarts live inside the ThoughtDAG app; this file only
21
+ // opens the door and forwards the current session id so the canvas can offer
22
+ // to mirror it. Same pattern as dsh-synapse: window.__ModuleLoader__.load
23
+ // with a module whose inject lists the client services it reads and whose
24
+ // apply touches the DOM.
17
25
 
18
26
  window.__ModuleLoader__.load({
19
27
  id: 'dsh-thoughtdag',
20
- factory: () => {
28
+ factory: (require) => {
21
29
  const module = { exports: {} }
22
30
 
23
- // uiWorkspace: the harness's directory picker (the OS dialog when the
24
- // harness runs on this machine, its in-app browser when reached remotely)
25
- module.exports.inject = ['sessions', 'uiWorkspace']
31
+ // the host's React, for the two slot components (the sidebar glyph and
32
+ // the panel page); absent on a host that lends none, which also has no
33
+ // panel slot to fill
34
+ let React = null
35
+ try { React = require('react') } catch { React = null }
36
+
37
+ // sessions: the current session for the canvas to mirror. uiWorkspace: the
38
+ // harness's directory picker (the OS dialog when the harness runs on this
39
+ // machine, its in-app browser when reached remotely). slots / locale /
40
+ // layout: the panel entry, its label in the host's language, and the
41
+ // switch back to the conversation.
42
+ module.exports.inject = ['sessions', 'uiWorkspace', 'slots', 'locale', 'layout']
26
43
  module.exports.apply = ctx => {
27
44
  const currentSession = () => {
28
45
  const snapshot = ctx.sessions.list.getSnapshot()
@@ -46,26 +63,15 @@ window.__ModuleLoader__.load({
46
63
  style.setAttribute('data-plugin-css', CSS_ID)
47
64
  document.head.append(style)
48
65
  }
49
- // The map overlay starts below the window's top strip: the desktop host
50
- // publishes --dsh-frame-top-clearance on its root (48px under the
51
- // hiddenInset traffic lights on macOS, the caption height on Windows,
52
- // 0 in native fullscreen). In that strip the plugin draws a title band
53
- // of its own: the ThoughtDAG name, the 对话|思维图 switch and the canvas's
54
- // name, draggable like the host's own chrome rows (data-window-drag),
55
- // starting past the traffic lights (--dsh-frame-leading-clearance while
56
- // the sidebar is collapsed, 84px otherwise, since the overlay covers the
57
- // sidebar too) and stopping short of the Windows caption buttons. So
58
- // the lights and the window controls are never covered, and nothing of
59
- // the host's session header shows through (#39). A browser tab has no
60
- // clearance: no band, the overlay fills the window, the canvas keeps its
61
- // own switch.
62
- // The floating pill sits in the window's top strip, centre-right, on every platform: 12px down in a
63
- // browser tab, vertically centred in the host's own strip on the desktop (40px on Windows, 48px on
64
- // macOS). It is no-drag, so the strip's drag region never takes its clicks (#55: pinned half into the
65
- // drag region without no-drag, its upper half was swallowed; moved below the strip, it landed in the
66
- // host's session header). Centre-right keeps it clear of the host's menu on the left and the window
67
- // controls on the right.
68
- style.textContent = '.dsh-td-switch{position:fixed;z-index:120;top:12px;left:60%;display:flex;align-items:center;gap:2px;transform:translateX(-50%);-webkit-app-region:no-drag;border:1px solid #d1d5db;border-radius:999px;background:rgba(255,255,255,.96);padding:3px;backdrop-filter:blur(10px)}.dsh-td-switch button{height:28px;border:0;border-radius:999px;background:transparent;padding:0 11px;color:#6b7280;font:600 12px Inter,system-ui,sans-serif;cursor:pointer;white-space:nowrap}.dsh-td-switch button:hover{background:#f3f4f6;color:#111827}.dsh-td-switch button.active{background:#111827;color:#fff}.dsh-td-switch .dsh-td-mark{display:inline-block;width:8px;height:8px;border-radius:50%;background:#6d5dfc;box-shadow:0 -7px 0 #a99cff,0 7px 0 #f0a35a;margin:0 6px 0 8px;flex:none}.dsh-td-switch[hidden]{display:none}html[data-platform] .dsh-td-switch,html[data-windows-titlebar] .dsh-td-switch{top:max(2px,calc((var(--dsh-frame-top-clearance,40px) - 36px) / 2))}.dsh-td-bar{position:fixed;z-index:121;top:0;left:0;right:0;height:var(--dsh-frame-top-clearance,0px);display:flex;align-items:center;gap:14px;padding:0 16px;box-sizing:border-box;background:#faf9f7;border-bottom:1px solid #e7e2d9;font:500 12px Inter,system-ui,sans-serif;color:#6b7280;overflow:hidden;-webkit-app-region:drag;user-select:none}.dsh-td-bar[hidden]{display:none}html[data-platform="darwin"] .dsh-td-bar{padding-left:max(var(--dsh-frame-leading-clearance,0px),84px)}html[data-windows-titlebar] .dsh-td-bar{padding-right:calc(100% - env(titlebar-area-width,100%) + 16px)}.dsh-td-bar .dsh-td-brand{display:flex;align-items:center;gap:7px;color:#111827;font-weight:600;font-size:13px;white-space:nowrap}.dsh-td-bar .dsh-td-brand i{display:inline-block;width:8px;height:8px;border-radius:50%;background:#6d5dfc;box-shadow:0 -7px 0 #a99cff,0 7px 0 #f0a35a}.dsh-td-bar .dsh-td-switch{position:static;transform:none;-webkit-app-region:no-drag}.dsh-td-bar .dsh-td-title{min-width:0;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:#6b7280}.dsh-td-overlay{position:fixed;z-index:100;inset:var(--dsh-frame-top-clearance,0px) 0 0 0;background:#faf9f7}.dsh-td-overlay[hidden]{display:none}.dsh-td-overlay iframe{display:block;width:100%;height:100%;border:0}'
66
+ // Panel mode: .dsh-td-page is the placeholder the host renders in its
67
+ // main column; on macOS it pads by the window's top strip (the host's
68
+ // own entry pages do the same) so the canvas toolbar never sits under
69
+ // the traffic lights. .dsh-td-overlay is the iframe's fixed layer, laid
70
+ // over the page by place() — no inset of its own.
71
+ // Fallback mode (no panel slot): the floating pill in the window's top
72
+ // strip, the macOS title band over the strip while the canvas is up
73
+ // (#39), the overlay filling the window below the strip.
74
+ style.textContent = '.dsh-td-page{display:block;width:100%;height:100%;min-height:100%;box-sizing:border-box}html[data-platform="darwin"] .dsh-td-page{padding-top:var(--dsh-frame-top-clearance,0px)}.dsh-td-overlay{position:fixed;z-index:100;background:#faf9f7}.dsh-td-overlay[hidden]{display:none}.dsh-td-overlay iframe{display:block;width:100%;height:100%;border:0}.dsh-td-overlay.dsh-td-full{inset:var(--dsh-frame-top-clearance,0px) 0 0 0}.dsh-td-switch{position:fixed;z-index:120;top:12px;left:60%;display:flex;align-items:center;gap:2px;transform:translateX(-50%);-webkit-app-region:no-drag;border:1px solid #d1d5db;border-radius:999px;background:rgba(255,255,255,.96);padding:3px;backdrop-filter:blur(10px)}.dsh-td-switch button{height:28px;border:0;border-radius:999px;background:transparent;padding:0 11px;color:#6b7280;font:600 12px Inter,system-ui,sans-serif;cursor:pointer;white-space:nowrap}.dsh-td-switch button:hover{background:#f3f4f6;color:#111827}.dsh-td-switch button.active{background:#111827;color:#fff}.dsh-td-switch .dsh-td-mark{display:inline-block;width:8px;height:8px;border-radius:50%;background:#6d5dfc;box-shadow:0 -7px 0 #a99cff,0 7px 0 #f0a35a;margin:0 6px 0 8px;flex:none}.dsh-td-switch[hidden]{display:none}html[data-platform] .dsh-td-switch,html[data-windows-titlebar] .dsh-td-switch{top:max(2px,calc((var(--dsh-frame-top-clearance,40px) - 36px) / 2))}.dsh-td-bar{position:fixed;z-index:121;top:0;left:0;right:0;height:var(--dsh-frame-top-clearance,0px);display:flex;align-items:center;gap:14px;padding:0 16px;box-sizing:border-box;background:#faf9f7;border-bottom:1px solid #e7e2d9;font:500 12px Inter,system-ui,sans-serif;color:#6b7280;overflow:hidden;-webkit-app-region:drag;user-select:none}.dsh-td-bar[hidden]{display:none}html[data-platform="darwin"] .dsh-td-bar{padding-left:max(var(--dsh-frame-leading-clearance,0px),84px)}html[data-windows-titlebar] .dsh-td-bar{padding-right:calc(100% - env(titlebar-area-width,100%) + 16px)}.dsh-td-bar .dsh-td-brand{display:flex;align-items:center;gap:7px;color:#111827;font-weight:600;font-size:13px;white-space:nowrap}.dsh-td-bar .dsh-td-brand i{display:inline-block;width:8px;height:8px;border-radius:50%;background:#6d5dfc;box-shadow:0 -7px 0 #a99cff,0 7px 0 #f0a35a}.dsh-td-bar .dsh-td-switch{position:static;transform:none;-webkit-app-region:no-drag}.dsh-td-bar .dsh-td-title{min-width:0;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:#6b7280}'
69
75
 
70
76
  let host = document.body.querySelector(':scope > [data-dsh-thoughtdag]')
71
77
  if (!host) {
@@ -73,7 +79,7 @@ window.__ModuleLoader__.load({
73
79
  host.setAttribute('data-dsh-thoughtdag', '')
74
80
  document.body.append(host)
75
81
  }
76
- host.innerHTML = '<div class="dsh-td-switch" role="group" aria-label="view switch"><i class="dsh-td-mark" title="ThoughtDAG"></i><button type="button" data-view="dialog" class="active" aria-pressed="true">对话</button><button type="button" data-view="map" aria-pressed="false">思维图</button></div><div class="dsh-td-bar" data-window-drag hidden><span class="dsh-td-brand"><i></i>ThoughtDAG</span><div class="dsh-td-switch" role="group" aria-label="view switch"><button type="button" data-view="dialog" aria-pressed="false">对话</button><button type="button" data-view="map" class="active" aria-pressed="true">思维图</button></div><span class="dsh-td-title"></span></div><section class="dsh-td-overlay" hidden><iframe title="ThoughtDAG" data-src="/thoughtdag/"></iframe></section>'
82
+ host.innerHTML = '<div class="dsh-td-switch" role="group" aria-label="view switch" hidden><i class="dsh-td-mark" title="ThoughtDAG"></i><button type="button" data-view="dialog" class="active" aria-pressed="true">对话</button><button type="button" data-view="map" aria-pressed="false">思维图</button></div><div class="dsh-td-bar" data-window-drag hidden><span class="dsh-td-brand"><i></i>ThoughtDAG</span><div class="dsh-td-switch" role="group" aria-label="view switch"><button type="button" data-view="dialog" aria-pressed="false">对话</button><button type="button" data-view="map" class="active" aria-pressed="true">思维图</button></div><span class="dsh-td-title"></span></div><section class="dsh-td-overlay" hidden><iframe title="ThoughtDAG" data-src="/thoughtdag/"></iframe></section>'
77
83
 
78
84
  // the plugin's version, for the canvas's update dialog and release history
79
85
  let pluginVersion = null
@@ -82,52 +88,139 @@ window.__ModuleLoader__.load({
82
88
  const switchEl = host.querySelector(':scope > .dsh-td-switch')
83
89
  const bar = host.querySelector('.dsh-td-bar')
84
90
  const barTitle = bar.querySelector('.dsh-td-title')
85
- // the desktop host marks its root; the band exists only there, and only while the strip has height (not in native fullscreen)
91
+ const dialogBtn = host.querySelector('[data-view="dialog"]')
92
+ const mapBtn = host.querySelector('[data-view="map"]')
93
+ const overlay = host.querySelector('.dsh-td-overlay')
94
+ const frame = host.querySelector('iframe')
95
+ // the desktop host marks its root; the strip is the window's top band (not in native fullscreen)
86
96
  const desktop = () => document.documentElement.dataset.platform !== undefined || document.documentElement.hasAttribute('data-windows-titlebar')
87
97
  const strip = () => parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--dsh-frame-top-clearance')) || 0
98
+ const send = (type, payload) => frame.contentWindow?.postMessage({ source: 'dsh-thoughtdag', type, ...payload }, location.origin)
99
+ const syncCurrent = () => send('td:current-session', { session: currentSession() })
100
+ // the SPA boots on first open, never while hidden: a canvas that
101
+ // measures itself inside a display:none frame fits its view to a 0×0
102
+ // box and shows nothing when revealed
103
+ const ensureSrc = () => { if (!frame.src) frame.src = frame.dataset.src + (pluginVersion ? (frame.dataset.src.includes('?') ? '&' : '?') + 'dv=' + encodeURIComponent(pluginVersion) : '') }
104
+
105
+ // ── panel mode: the host's sidebar entry and main-column page ──
106
+ const PANEL_ID = 'thoughtdag'
107
+ const NS = 'dsh-thoughtdag'
108
+ let panelMode = false
109
+ // the page element while the panel is selected (null otherwise)
110
+ let pageEl = null
111
+ let pageObserver = null
112
+ const place = () => {
113
+ if (!pageEl) return
114
+ let box = pageEl
115
+ // the page fills its column; should a host give it no height, the
116
+ // nearest ancestor with one is the column
117
+ for (let i = 0; i < 3 && box.getBoundingClientRect().height < 40 && box.parentElement; i++) box = box.parentElement
118
+ const r = box.getBoundingClientRect()
119
+ const pad = box === pageEl ? parseFloat(getComputedStyle(pageEl).paddingTop) || 0 : 0
120
+ overlay.style.left = r.left + 'px'
121
+ overlay.style.top = (r.top + pad) + 'px'
122
+ overlay.style.width = r.width + 'px'
123
+ overlay.style.height = Math.max(0, r.height - pad) + 'px'
124
+ }
125
+ const openPanel = el => {
126
+ pageEl = el
127
+ ensureSrc()
128
+ overlay.classList.remove('dsh-td-full')
129
+ overlay.hidden = false
130
+ place()
131
+ pageObserver = new ResizeObserver(place)
132
+ pageObserver.observe(el)
133
+ if (el.parentElement) pageObserver.observe(el.parentElement)
134
+ syncCurrent()
135
+ // no band: the host's own column is the canvas's edge; the SPA shows its own switch
136
+ send('td:view', { shown: true, bar: false, desktop: desktop(), panel: true })
137
+ window.setTimeout(() => { if (pageEl) { syncCurrent(); send('td:view', { shown: true, bar: false, desktop: desktop(), panel: true }) } }, 400)
138
+ }
139
+ const closePanel = () => {
140
+ pageEl = null
141
+ pageObserver?.disconnect()
142
+ pageObserver = null
143
+ overlay.hidden = true
144
+ send('td:view', { shown: false, bar: false, desktop: desktop(), panel: true })
145
+ }
146
+ // set once the host has declared the panel slots and taken our entries; a
147
+ // host that never declares them (older harnesses) gets the fallback below
148
+ let registered = false
149
+ if (React && ctx.slots && ctx.layout && ctx.locale && typeof ctx.effect === 'function') {
150
+ try {
151
+ const h = React.createElement
152
+ // the three-dot mark, as the sidebar glyph
153
+ const PanelIcon = ({ size }) => h('svg', { width: size ?? 16, height: size ?? 16, viewBox: '0 0 16 16', 'aria-hidden': 'true' },
154
+ h('circle', { cx: 8, cy: 3, r: 2.2, fill: '#a99cff' }),
155
+ h('circle', { cx: 8, cy: 8, r: 2.2, fill: '#6d5dfc' }),
156
+ h('circle', { cx: 8, cy: 13, r: 2.2, fill: '#f0a35a' }))
157
+ // the page: a placeholder the iframe layer is laid over while mounted
158
+ const PanelPage = () => {
159
+ const ref = React.useRef(null)
160
+ React.useEffect(() => {
161
+ if (ref.current) openPanel(ref.current)
162
+ return () => closePanel()
163
+ }, [])
164
+ return h('div', { ref, className: 'dsh-td-page', 'data-td-page': '' })
165
+ }
166
+ ctx.effect(() => ctx.locale.register(NS, { zh: { panel: 'ThoughtDAG' }, en: { panel: 'ThoughtDAG' } }), 'dsh-thoughtdag: dictionaries')
167
+ const t = ctx.locale.bind(NS)
168
+ ctx.slots.inject('main', () => ctx.slots.register({ name: 'main', key: PANEL_ID, locale: NS, inject: () => ({}) }, PanelPage))
169
+ ctx.slots.inject('sidebar.panellist', () => { const dispose = ctx.slots.register({ name: 'sidebar.panellist', id: PANEL_ID, order: 30, locale: NS, label: () => t('panel') }, PanelIcon); registered = true; return dispose })
170
+ panelMode = true
171
+ } catch (error) {
172
+ console.warn('[dsh-thoughtdag] panel entry unavailable, using the floating switch:', error)
173
+ panelMode = false
174
+ }
175
+ }
176
+
177
+ // ── fallback mode: the floating pill and the full-window overlay ──
178
+ let spaReady = false
88
179
  // the band only on macOS: there the strip under the traffic lights is transparent and the host's session
89
180
  // header would show through the overlay (#39). On Windows the strip is the host's own chrome (its menu,
90
181
  // the caption buttons), opaque and above us; a band there painted over the host's menu (#55). With no
91
182
  // band the overlay starts below the chrome and the canvas shows its own switch.
92
- const barUp = () => document.documentElement.dataset.platform === 'darwin' && strip() > 0
93
- let spaReady = false
94
- const dialogBtn = host.querySelector('[data-view="dialog"]')
95
- const mapBtn = host.querySelector('[data-view="map"]')
96
- const overlay = host.querySelector('.dsh-td-overlay')
97
- const frame = host.querySelector('iframe')
98
-
183
+ const barUp = () => !panelMode && document.documentElement.dataset.platform === 'darwin' && strip() > 0
99
184
  const setView = map => {
100
185
  dialogBtn.classList.toggle('active', !map)
101
186
  dialogBtn.setAttribute('aria-pressed', String(!map))
102
187
  mapBtn.classList.toggle('active', map)
103
188
  mapBtn.setAttribute('aria-pressed', String(map))
104
189
  }
105
- const close = () => { overlay.hidden = true; bar.hidden = true; switchEl.hidden = false; setView(false); send('td:view', { shown: false, bar: false, desktop: desktop() }) }
190
+ const closeFallback = () => { overlay.hidden = true; bar.hidden = true; switchEl.hidden = false; setView(false); send('td:view', { shown: false, bar: false, desktop: desktop() }) }
106
191
  const showBand = () => { bar.hidden = !barUp(); send('td:view', { shown: true, bar: !bar.hidden, desktop: desktop() }) }
107
- window.addEventListener('resize', () => { if (!overlay.hidden) showBand() })
108
- bar.querySelector('[data-view="dialog"]').addEventListener('click', () => close())
109
- const send = (type, payload) => frame.contentWindow?.postMessage({ source: 'dsh-thoughtdag', type, ...payload }, location.origin)
110
-
111
- const syncCurrent = () => {
112
- const session = currentSession()
113
- send('td:current-session', { session })
114
- }
115
-
116
- mapBtn.addEventListener('click', () => {
192
+ const openFallback = () => {
193
+ overlay.classList.add('dsh-td-full')
194
+ overlay.style.left = overlay.style.top = overlay.style.width = overlay.style.height = ''
117
195
  overlay.hidden = false
118
196
  setView(true)
119
197
  bar.hidden = !barUp()
120
198
  if (spaReady || !bar.hidden) switchEl.hidden = true
121
- // the SPA boots on first open, never while hidden: a canvas that
122
- // measures itself inside a display:none frame fits its view to a 0×0
123
- // box and shows nothing when revealed
124
- if (!frame.src) frame.src = frame.dataset.src + (pluginVersion ? (frame.dataset.src.includes('?') ? '&' : '?') + 'dv=' + encodeURIComponent(pluginVersion) : '')
199
+ ensureSrc()
125
200
  syncCurrent()
126
201
  showBand()
127
202
  // let the SPA boot, then re-sync so its listener is ready
128
203
  window.setTimeout(() => { syncCurrent(); showBand() }, 400)
129
- })
130
- dialogBtn.addEventListener('click', close)
204
+ }
205
+ let fallbackOn = false
206
+ const enableFallback = () => {
207
+ if (fallbackOn) return
208
+ fallbackOn = true
209
+ panelMode = false
210
+ switchEl.hidden = false
211
+ window.addEventListener('resize', () => { if (!overlay.hidden) showBand() })
212
+ bar.querySelector('[data-view="dialog"]').addEventListener('click', closeFallback)
213
+ mapBtn.addEventListener('click', openFallback)
214
+ dialogBtn.addEventListener('click', closeFallback)
215
+ }
216
+ if (!panelMode) enableFallback()
217
+ // the slot system takes registrations only once the host declares the
218
+ // slot; a host that has not declared sidebar.panellist within a moment
219
+ // of our apply has no panel list at all, so the floating switch returns
220
+ else window.setTimeout(() => { if (!registered) { console.warn('[dsh-thoughtdag] no sidebar panel list on this host, using the floating switch'); enableFallback() } }, 2500)
221
+
222
+ // back to the conversation: the host's own navigation in panel mode, the overlay's close otherwise
223
+ const close = () => { if (panelMode) { try { ctx.layout.selectPanel(null) } catch { closePanel() } } else closeFallback() }
131
224
 
132
225
  window.addEventListener('message', event => {
133
226
  if (event.origin !== location.origin || event.data?.source !== 'dsh-thoughtdag') return
@@ -148,12 +241,13 @@ window.__ModuleLoader__.load({
148
241
  return
149
242
  }
150
243
  if (event.data.type === 'td:request-current') {
151
- // the SPA has booted and shows its own switch: the pill steps aside
244
+ // the SPA has booted and shows its own switch: in fallback mode the pill steps aside
152
245
  spaReady = true
153
- if (!overlay.hidden) { switchEl.hidden = true; showBand() }
246
+ if (!panelMode && !overlay.hidden) { switchEl.hidden = true; showBand() }
247
+ if (panelMode && pageEl) send('td:view', { shown: true, bar: false, desktop: desktop(), panel: true })
154
248
  return syncCurrent()
155
249
  }
156
- // the canvas names its project: the band shows it
250
+ // the canvas names its project: the band shows it (fallback mode only)
157
251
  if (event.data.type === 'td:title') { barTitle.textContent = typeof event.data.name === 'string' ? event.data.name : ''; return }
158
252
  // the canvas forked or continued a session: stage it and go back to the
159
253
  // chat, which now shows exactly the context the canvas produced
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-thoughtdag",
3
- "version": "0.5.18",
3
+ "version": "0.5.19",
4
4
  "description": "ThoughtDAG for DeepSeek Harness: turn the current DSH session into an editable thought graph on an infinite canvas. Source, releases and issues: github.com/chenxiachan/thoughtdag",
5
5
  "author": "Xia Chen",
6
6
  "repository": {