fix: session-cwd attach race, terminal failure surfacing, white-screen hardening; CodeMirror editor

- cwd resolution falls back (attached header → client summary cwd → process
  cwd) across the JSON API, media route and terminal upgrade, so explorer/
  git/terminal work from first paint instead of 'session ... has no working
  directory' while the web client hydrates the session (refresh-only fix).
- terminal client threads the summary cwd into the WS URL and stops the
  reconnect loop after three unreasoned failures, showing the close code
  with a manual retry; a 1011+reason refusal already showed the reason.
- white screen: persisted state is structurally sanitized on load (malformed
  shapes fall back to default) and the restored width is clamped to the
  viewport; the layout margin is capped at the viewport; the client entry
  wraps every lifecycle step in try/catch and pins a visible diagnostic bar
  on load/mount/interception failure instead of failing silently.
- editor: textarea replaced with CodeMirror 6 — line wrapping, per-extension
  syntax highlighting (js/ts/jsx/tsx, json, python, html, css, xml, yaml,
  sql, java, c/c++, rust, go, php, shell, toml, nginx, dockerfile,
  properties), one-dark theme, dirty dot + Ctrl/Cmd+S save kept.
- tests: +9 (cwd fallback chain over the API route, state sanitization,
  language mapping); 36 pass, typecheck + build + vm materialize clean.
This commit is contained in:
Menghuan1918
2026-08-08 15:12:56 +08:00
parent 8950e2ae69
commit 47ebffcf9b
17 changed files with 1063 additions and 140 deletions
+46 -14
View File
@@ -57,20 +57,52 @@ class SidebarBoundary extends Component<{ children: ReactNode }, { error: string
* @param ctx - the client cordis context (slots, sessions).
*/
export function apply(ctx: Context): void {
ctx.effect(() => {
const host = document.createElement('div')
host.setAttribute('data-dsh-better-sidebar', '')
document.body.appendChild(host)
const root = createRoot(host)
root.render(createElement(SidebarBoundary, null, createElement(Sidebar, { ctx })))
return () => {
root.unmount()
host.remove()
// A failure anywhere in the client lifecycle must never take the app down
// silently: log with the plugin prefix and pin a visible diagnostic strip
// to the page so a blank panel is never the only symptom.
const fail = (phase: string, error: unknown): void => {
console.error(`[dsh-better-sidebar] ${phase} error:`, error)
try {
const bar = document.createElement('div')
bar.style.cssText = 'position:fixed;left:8px;bottom:8px;z-index:2147483000;max-width:70vw;padding:8px 12px;'
+ 'font:12px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;color:#f2a1a1;background:#1b1b22;'
+ 'border:1px solid #f2a1a1;border-radius:8px;white-space:pre-wrap'
bar.textContent = `[dsh-better-sidebar] ${phase} error: ${error instanceof Error ? error.message : String(error)}`
document.body.appendChild(bar)
} catch {
// Nothing left to report with.
}
}, 'dsh-better-sidebar: sidebar mount')
}
try {
ctx.effect(() => {
try {
const host = document.createElement('div')
host.setAttribute('data-dsh-better-sidebar', '')
document.body.appendChild(host)
const root = createRoot(host)
root.render(createElement(SidebarBoundary, null, createElement(Sidebar, { ctx })))
return () => {
root.unmount()
host.remove()
}
} catch (error) {
fail('mount', error)
return undefined
}
}, 'dsh-better-sidebar: sidebar mount')
ctx.effect(
() => registerTurnTailInterception(ctx),
'dsh-better-sidebar: turn-tail interception',
)
ctx.effect(
() => {
try {
return registerTurnTailInterception(ctx)
} catch (error) {
fail('interception', error)
return undefined
}
},
'dsh-better-sidebar: turn-tail interception',
)
} catch (error) {
fail('load', error)
}
}