@yadsh/dsh-qa-surface 0.1.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 (61) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/LICENSE +21 -0
  3. package/README.md +135 -0
  4. package/SPEC-dsh-qa-surface.md +1921 -0
  5. package/compatibility.json +15 -0
  6. package/cordis.patch.yml +9 -0
  7. package/docs/ARCHITECTURE.md +24 -0
  8. package/docs/COMPATIBILITY.md +13 -0
  9. package/docs/CONFIGURATION.md +18 -0
  10. package/docs/EMBEDDING.md +13 -0
  11. package/lib/client/QaConfigController.js +65 -0
  12. package/lib/client/QaConfigController.js.map +1 -0
  13. package/lib/client/QaRouteController.js +109 -0
  14. package/lib/client/QaRouteController.js.map +1 -0
  15. package/lib/client/QaSessionController.js +366 -0
  16. package/lib/client/QaSessionController.js.map +1 -0
  17. package/lib/client/QaSurface.js +109 -0
  18. package/lib/client/QaSurface.js.map +1 -0
  19. package/lib/client/QaTranscriptAdapter.js +92 -0
  20. package/lib/client/QaTranscriptAdapter.js.map +1 -0
  21. package/lib/client/components/Markdown.js +102 -0
  22. package/lib/client/components/Markdown.js.map +1 -0
  23. package/lib/client/components/QaComposer.js +33 -0
  24. package/lib/client/components/QaComposer.js.map +1 -0
  25. package/lib/client/components/QaMessage.js +14 -0
  26. package/lib/client/components/QaMessage.js.map +1 -0
  27. package/lib/client/index.js +46 -0
  28. package/lib/client/index.js.map +1 -0
  29. package/lib/client/styles.js +58 -0
  30. package/lib/client/styles.js.map +1 -0
  31. package/lib/client.js +1131 -0
  32. package/lib/client.js.map +1 -0
  33. package/lib/config.js +83 -0
  34. package/lib/config.js.map +1 -0
  35. package/lib/host-route.js +27 -0
  36. package/lib/host-route.js.map +1 -0
  37. package/lib/index.js +83 -0
  38. package/lib/index.js.map +1 -0
  39. package/lib/navigation-marker.js +3 -0
  40. package/lib/navigation-marker.js.map +1 -0
  41. package/lib/resolve-config.js +143 -0
  42. package/lib/resolve-config.js.map +1 -0
  43. package/lib/types/client/QaConfigController.d.ts +21 -0
  44. package/lib/types/client/QaRouteController.d.ts +43 -0
  45. package/lib/types/client/QaSessionController.d.ts +59 -0
  46. package/lib/types/client/QaSurface.d.ts +16 -0
  47. package/lib/types/client/QaTranscriptAdapter.d.ts +7 -0
  48. package/lib/types/client/components/Markdown.d.ts +5 -0
  49. package/lib/types/client/components/QaComposer.d.ts +11 -0
  50. package/lib/types/client/components/QaMessage.d.ts +8 -0
  51. package/lib/types/client/index.d.ts +19 -0
  52. package/lib/types/client/styles.d.ts +2 -0
  53. package/lib/types/config.d.ts +5 -0
  54. package/lib/types/host-route.d.ts +9 -0
  55. package/lib/types/index.d.ts +29 -0
  56. package/lib/types/navigation-marker.d.ts +3 -0
  57. package/lib/types/resolve-config.d.ts +6 -0
  58. package/lib/types/types.d.ts +114 -0
  59. package/lib/types.js +2 -0
  60. package/lib/types.js.map +1 -0
  61. package/package.json +137 -0
@@ -0,0 +1,1921 @@
1
+ # SPEC / Implementation Plan: `dsh-qa-surface`
2
+
3
+ **Status:** Draft / implementation-ready
4
+ **Date:** 2026-09-05
5
+ **Target:** DeepSeek Harness Web
6
+ **Artifact type:** external DSH UI plugin with a dedicated `/qa` browser surface
7
+
8
+ ---
9
+
10
+ ## 1. Goal
11
+
12
+ Create a DeepSeek Harness plugin named **`dsh-qa-surface`** that exposes a minimal, embeddable QA/chat interface at:
13
+
14
+ ```text
15
+ /qa
16
+ ```
17
+
18
+ The page must look like a standalone assistant rather than the standard DSH developer UI:
19
+
20
+ - one conversation;
21
+ - user and assistant messages;
22
+ - text input;
23
+ - Send / Stop;
24
+ - optional reset/new chat;
25
+ - optional welcome text and suggested questions;
26
+ - no workspace tree;
27
+ - no settings;
28
+ - no model picker by default;
29
+ - no tool-call tree;
30
+ - no reasoning/trajectory UI;
31
+ - no developer-oriented DSH controls.
32
+
33
+ At the same time, the assistant must continue to run through the **real DSH runtime** and retain DSH capabilities such as:
34
+
35
+ - Sessions;
36
+ - Agent / Agent Loop;
37
+ - model providers and routing;
38
+ - system prompt and agent preset;
39
+ - tools;
40
+ - skills;
41
+ - MCP;
42
+ - memory/knowledge plugins;
43
+ - subagents;
44
+ - persistence;
45
+ - telemetry;
46
+ - permission policy;
47
+ - other Host-side plugins composed into the selected agent/session.
48
+
49
+ The plugin must **not** create a second agent runtime or proxy prompts to an LLM directly.
50
+
51
+ ---
52
+
53
+ ## 2. Primary design decision
54
+
55
+ ### 2.1 MVP implementation
56
+
57
+ Implement `/qa` as a **full-screen browser UI plugin occupying the additive `shell.overlay` slot** when `window.location.pathname` matches the configured QA route.
58
+
59
+ The normal DSH Web application continues to boot underneath it.
60
+
61
+ ```text
62
+ GET /qa
63
+
64
+
65
+ DSH frontend-static SPA fallback
66
+
67
+
68
+ standard DSH index.html + window.__DSH_BOOT__
69
+
70
+
71
+ normal browser plugin graph
72
+
73
+ ├── ui-layout
74
+ ├── ui-conversation
75
+ ├── client-runtime
76
+ ├── connection
77
+ └── dsh-qa-surface
78
+
79
+ └── shell.overlay → fullscreen QA surface
80
+ ```
81
+
82
+ This is intentionally preferred over replacing DSH's entire `root`, `sidebar`, or `conversation` slots.
83
+
84
+ ### Why
85
+
86
+ Current DSH layout exposes the frame-level seats:
87
+
88
+ - `sidebar` — `single`, root scope;
89
+ - `conversation` — `single`, session-maybe scope;
90
+ - `details` — `single`, session scope;
91
+ - `shell.overlay` — additive `list`, root scope.
92
+
93
+ There is currently no first-class generic "global page" route/slot for third-party plugins. Replacing `sidebar`, `conversation`, or the entire root would make the plugin compete with first-party occupants and is more likely to break on DSH updates.
94
+
95
+ `conversation.session` is useful for replacing the body of a selected session, but by itself it does not provide a top-level page and does not remove the surrounding frame.
96
+
97
+ Therefore MVP should use the additive full-frame overlay and keep all host/runtime services native.
98
+
99
+ ### 2.2 Future implementation
100
+
101
+ Once the plugin stabilizes, optionally add a second delivery mode:
102
+
103
+ ```text
104
+ qa profile / bundle
105
+ ├── dsh-base
106
+ ├── web transport
107
+ ├── client runtime
108
+ └── qa-only browser roster
109
+ ```
110
+
111
+ That profile can remove unused first-party UI plugins entirely. It is **not required for MVP**.
112
+
113
+ ---
114
+
115
+ ## 3. Verified DSH architecture assumptions
116
+
117
+ The implementation plan below relies on the following current DSH behavior as of 2026-09-05.
118
+
119
+ ### 3.1 DSH client UI is plugin-based
120
+
121
+ A browser UI plugin is a dual-face package:
122
+
123
+ ```text
124
+ src/index.ts Host / Node half
125
+ src/client/index.ts Browser half
126
+ ```
127
+
128
+ The package declares a `dsh.client` manifest and exports `./client`.
129
+
130
+ The Host-side client module registry discovers enabled Loader entries with `dsh.client`, serves their bundles under `/plugins/...`, and injects the client module graph into `window.__DSH_BOOT__`.
131
+
132
+ ### 3.2 `/qa` does not need a separate static server
133
+
134
+ The shipped `dsh-host-frontend-static` owns the webserver fallback seat.
135
+
136
+ For GET/HEAD requests, a missing static path falls back to `index.html` with HTTP 200. Therefore:
137
+
138
+ ```text
139
+ GET /qa
140
+ ```
141
+
142
+ can already boot the same DSH SPA shell without adding a new Vite server or replacing the fallback handler.
143
+
144
+ ### 3.3 Session Controller remains authoritative
145
+
146
+ Session lifecycle, prompt execution, history and live events remain Host-owned. The client runtime mirrors them and exposes session objects/snapshots.
147
+
148
+ The QA surface must use this existing object/API layer instead of inventing a second transcript state machine.
149
+
150
+ ### 3.4 Do not import private first-party React components
151
+
152
+ Cross-plugin cooperation should happen through:
153
+
154
+ - Cordis services;
155
+ - typed slots;
156
+ - public client/runtime contracts;
157
+ - public Session/Conversation faces.
158
+
159
+ Do **not** deep-import internal DSH components such as private `MessageItem`, private composer internals, or AppFrame implementation files.
160
+
161
+ ### 3.5 DSH is still developer-preview software
162
+
163
+ Compatibility-breaking changes are expected upstream.
164
+
165
+ All DSH-specific calls must therefore be isolated behind small adapter/controller modules inside this plugin.
166
+
167
+ ---
168
+
169
+ ## 4. User-facing behavior
170
+
171
+ ### 4.1 `/`
172
+
173
+ Normal DSH Web remains untouched.
174
+
175
+ ```text
176
+ https://dsh.example.com/
177
+ ```
178
+
179
+ shows the standard DSH developer UI.
180
+
181
+ ### 4.2 `/qa`
182
+
183
+ ```text
184
+ https://dsh.example.com/qa
185
+ ```
186
+
187
+ shows only the QA assistant surface.
188
+
189
+ Example:
190
+
191
+ ```text
192
+ ┌───────────────────────────────────────────────┐
193
+ │ Acme Assistant │
194
+ │ Ask a question about the internal platform. │
195
+ ├───────────────────────────────────────────────┤
196
+ │ │
197
+ │ Assistant │
198
+ │ Hi. How can I help? │
199
+ │ │
200
+ │ User │
201
+ │ How do I reset...? │
202
+ │ │
203
+ │ Assistant │
204
+ │ To reset... │
205
+ │ │
206
+ ├───────────────────────────────────────────────┤
207
+ │ Ask a question... Send │
208
+ └───────────────────────────────────────────────┘
209
+ ```
210
+
211
+ No first-party DSH navigation or admin controls should be visible through the QA surface.
212
+
213
+ ---
214
+
215
+ ## 5. Scope
216
+
217
+ ### MVP must include
218
+
219
+ - `/qa` route recognition;
220
+ - full-screen QA overlay;
221
+ - responsive layout;
222
+ - automatic creation/restoration of one DSH Session;
223
+ - text prompts;
224
+ - streamed assistant text;
225
+ - persisted transcript through DSH Session persistence;
226
+ - loading/running state;
227
+ - Stop/Cancel;
228
+ - New Chat / Reset;
229
+ - Markdown rendering;
230
+ - basic errors and reconnect states;
231
+ - configurable title/welcome/placeholder;
232
+ - configurable fixed workspace/session target policy;
233
+ - configurable agent preset;
234
+ - configurable model override if explicitly set;
235
+ - safe handling of unsupported interactive states;
236
+ - no leakage of tool calls/reasoning into the QA transcript by default;
237
+ - same-origin use of the normal DSH connection/API stack;
238
+ - desktop and mobile layout;
239
+ - package build/install instructions;
240
+ - tests.
241
+
242
+ ### Post-MVP candidates
243
+
244
+ - image/file attachments;
245
+ - suggested-question chips;
246
+ - source/citation cards;
247
+ - compact tool-status indicators;
248
+ - explicit `ask_user_question` support;
249
+ - explicit approval UI;
250
+ - conversation rating/feedback;
251
+ - custom branding/logo;
252
+ - iframe embedding helper;
253
+ - embeddable JS launcher/widget;
254
+ - per-user session mapping;
255
+ - anonymous guest sessions behind a dedicated auth gateway;
256
+ - dedicated `qa` DSH profile/bundle;
257
+ - multiple assistant presets selected by URL;
258
+ - public conversation links.
259
+
260
+ ---
261
+
262
+ ## 6. Non-goals
263
+
264
+ The MVP must **not**:
265
+
266
+ - fork DeepSeek Harness;
267
+ - modify DSH built frontend files after every update;
268
+ - start a second Vite/React server for production;
269
+ - implement its own LLM provider calls;
270
+ - duplicate DSH Agent Loop;
271
+ - duplicate DSH Session persistence;
272
+ - bypass DSH browser trust/auth mechanisms;
273
+ - enable unrestricted CORS;
274
+ - automatically approve permission requests;
275
+ - automatically answer agent questions;
276
+ - expose admin/settings APIs to unauthenticated users;
277
+ - expose raw reasoning by default;
278
+ - expose raw tool arguments/results by default;
279
+ - depend on private DSH component implementation details;
280
+ - take over first-party `sidebar`, `conversation`, `details`, or `root` seats globally.
281
+
282
+ ---
283
+
284
+ ## 7. Package name
285
+
286
+ Preferred npm/package name:
287
+
288
+ ```text
289
+ dsh-qa-surface
290
+ ```
291
+
292
+ If using an npm scope:
293
+
294
+ ```text
295
+ @<scope>/dsh-qa-surface
296
+ ```
297
+
298
+ Repository topic:
299
+
300
+ ```text
301
+ dsh-plugin
302
+ ```
303
+
304
+ ---
305
+
306
+ ## 8. Proposed repository structure
307
+
308
+ ```text
309
+ dsh-qa-surface/
310
+ ├── package.json
311
+ ├── README.md
312
+ ├── LICENSE
313
+ ├── CHANGELOG.md
314
+ ├── tsconfig.json
315
+ ├── tsconfig.host.json
316
+ ├── tsconfig.client.json
317
+ ├── tsdown.config.ts
318
+ ├── cordis.patch.yml # example install/composition patch
319
+ ├── docs/
320
+ │ ├── ARCHITECTURE.md
321
+ │ ├── CONFIGURATION.md
322
+ │ ├── EMBEDDING.md
323
+ │ └── COMPATIBILITY.md
324
+ ├── src/
325
+ │ ├── index.ts # Host half
326
+ │ ├── config.ts
327
+ │ ├── settings.ts
328
+ │ ├── invariant.ts
329
+ │ └── client/
330
+ │ ├── index.ts # browser apply()/inject
331
+ │ ├── QaSurface.tsx
332
+ │ ├── QaSessionController.ts
333
+ │ ├── QaRouteController.ts
334
+ │ ├── QaConfigController.ts
335
+ │ ├── QaTranscriptAdapter.ts
336
+ │ ├── components/
337
+ │ │ ├── QaHeader.tsx
338
+ │ │ ├── QaTranscript.tsx
339
+ │ │ ├── QaMessage.tsx
340
+ │ │ ├── QaComposer.tsx
341
+ │ │ ├── QaStatus.tsx
342
+ │ │ ├── QaError.tsx
343
+ │ │ └── SuggestedQuestions.tsx
344
+ │ ├── stores/
345
+ │ │ ├── qa-route-store.ts
346
+ │ │ ├── qa-session-store.ts
347
+ │ │ └── qa-config-store.ts
348
+ │ ├── styles/
349
+ │ │ └── qa-surface.module.css
350
+ │ └── types.ts
351
+ └── test/
352
+ ├── config.test.ts
353
+ ├── route.test.ts
354
+ ├── session-controller.test.ts
355
+ ├── transcript-adapter.test.ts
356
+ ├── client-smoke.test.tsx
357
+ └── e2e/
358
+ ├── qa-route.spec.ts
359
+ ├── qa-stream.spec.ts
360
+ ├── qa-reload.spec.ts
361
+ └── qa-error.spec.ts
362
+ ```
363
+
364
+ The exact external-build tsdown setup may need to mirror DSH's lazy-CJS client bundle format because DSH does not currently publish a general external client build preset.
365
+
366
+ ---
367
+
368
+ ## 9. `package.json` requirements
369
+
370
+ The package must expose both halves.
371
+
372
+ Conceptual shape:
373
+
374
+ ```json
375
+ {
376
+ "name": "dsh-qa-surface",
377
+ "type": "module",
378
+ "exports": {
379
+ ".": {
380
+ "types": "./lib/types/index.d.ts",
381
+ "default": "./lib/index.js"
382
+ },
383
+ "./client": {
384
+ "types": "./lib/types/client/index.d.ts",
385
+ "default": "./lib/client.js"
386
+ },
387
+ "./package.json": "./package.json"
388
+ },
389
+ "dsh": {
390
+ "client": {
391
+ "platform": "web",
392
+ "inject": [
393
+ "@deepseek-ai/dsh-client-runtime",
394
+ "@deepseek-ai/dsh-client-ui-layout",
395
+ "@deepseek-ai/dsh-client-ui-theme"
396
+ ]
397
+ }
398
+ }
399
+ }
400
+ ```
401
+
402
+ The exact dependency edge list must be verified against the installed DSH version during implementation.
403
+
404
+ Do not add dependencies merely to obtain private UI components.
405
+
406
+ ---
407
+
408
+ ## 10. Host half
409
+
410
+ The Host half should be intentionally small.
411
+
412
+ Responsibilities:
413
+
414
+ 1. declare and validate plugin config;
415
+ 2. register a DSH settings namespace for user-configurable values;
416
+ 3. expose only the sanitized config required by the browser through supported DSH settings/client mechanisms;
417
+ 4. optionally register plugin diagnostics/invariant information;
418
+ 5. do not create sessions at startup;
419
+ 6. do not create another HTTP server;
420
+ 7. do not register a second SPA fallback;
421
+ 8. do not change `/api` transport security.
422
+
423
+ ### 10.1 Config source
424
+
425
+ Use standard DSH/Cordis composition config as defaults and integrate with the DSH settings subsystem where practical.
426
+
427
+ Preferred namespace:
428
+
429
+ ```text
430
+ qa-surface
431
+ ```
432
+
433
+ The browser boot graph itself contains package ids/URLs/dependency edges, not arbitrary plugin config. Therefore do **not** assume Host Cordis config magically arrives as arguments to the browser half.
434
+
435
+ Preferred config transport:
436
+
437
+ - Host registers a normal settings namespace;
438
+ - Browser reads it through the supported DSH settings/client service;
439
+ - settings writes remain subject to normal DSH permissions/trust.
440
+
441
+ Avoid creating an unauthenticated `/qa-config.json` endpoint.
442
+
443
+ ---
444
+
445
+ ## 11. Configuration model
446
+
447
+ Proposed initial schema:
448
+
449
+ ```yaml
450
+ enabled: true
451
+
452
+ route:
453
+ path: /qa
454
+ matchChildren: true
455
+
456
+ branding:
457
+ title: Assistant
458
+ subtitle: ""
459
+ welcomeMessage: "How can I help?"
460
+ placeholder: "Ask a question..."
461
+ logoUrl: null
462
+
463
+ session:
464
+ policy: browser-persistent
465
+ storageKey: dsh-qa-surface.session
466
+ workspaceId: null
467
+ fixedSessionId: null
468
+ agentPreset: null
469
+ provider: null
470
+ model: null
471
+ reasoningEffort: null
472
+
473
+ ui:
474
+ showHeader: true
475
+ showReset: true
476
+ showStop: true
477
+ showTimestamps: false
478
+ showToolActivity: false
479
+ showReasoning: false
480
+ renderMarkdown: true
481
+ maxContentWidth: 900
482
+
483
+ suggestedQuestions: []
484
+
485
+ interaction:
486
+ approvals: blocked
487
+ questions: unsupported
488
+
489
+ embedding:
490
+ frameAncestors: null
491
+ ```
492
+
493
+ ### 11.1 `route.path`
494
+
495
+ Default:
496
+
497
+ ```text
498
+ /qa
499
+ ```
500
+
501
+ Normalize:
502
+
503
+ - must start with `/`;
504
+ - remove trailing slash except root;
505
+ - reject `/api` and `/plugins` prefixes;
506
+ - reject empty strings.
507
+
508
+ ### 11.2 Session policies
509
+
510
+ Support three policy names in schema, but MVP only needs to fully implement the first two.
511
+
512
+ #### `browser-persistent` — default
513
+
514
+ - first visit creates a DSH Session;
515
+ - store its id in browser storage;
516
+ - reload/revisit restores it if the Host still knows the Session;
517
+ - invalid/missing session id creates a replacement;
518
+ - Reset creates a new Session and replaces the stored id.
519
+
520
+ #### `new-on-load`
521
+
522
+ - every fresh page load creates a new Session;
523
+ - SPA navigation within the same page may keep it until leaving `/qa`;
524
+ - suitable for kiosk/demo use.
525
+
526
+ #### `fixed`
527
+
528
+ - use `fixedSessionId`;
529
+ - intended only for controlled internal deployments;
530
+ - must never silently create that id if access policy refuses it;
531
+ - not recommended for multiple independent users.
532
+
533
+ ### 11.3 Workspace binding
534
+
535
+ For a deterministic QA assistant, a configured workspace is preferred.
536
+
537
+ MVP config:
538
+
539
+ ```yaml
540
+ session:
541
+ workspaceId: <configured DSH workspace id>
542
+ ```
543
+
544
+ If `workspaceId` is absent, implementation may fall back to DSH's normal recent/current workspace logic only when that behavior is explicitly documented in the README.
545
+
546
+ Do not silently bind to an arbitrary workspace if multiple are present.
547
+
548
+ ### 11.4 Agent preset
549
+
550
+ If configured:
551
+
552
+ ```yaml
553
+ session:
554
+ agentPreset: qa-assistant
555
+ ```
556
+
557
+ new QA sessions must use that preset.
558
+
559
+ This is the preferred place to encode:
560
+
561
+ - QA system prompt;
562
+ - skills;
563
+ - tools;
564
+ - knowledge integrations;
565
+ - permission policy;
566
+ - model policy.
567
+
568
+ Do not stuff all assistant behavior into the UI plugin.
569
+
570
+ ### 11.5 Model selection
571
+
572
+ Default:
573
+
574
+ ```yaml
575
+ provider: null
576
+ model: null
577
+ ```
578
+
579
+ means use DSH deployment/session defaults.
580
+
581
+ If both are configured, apply the model selection to the QA session through the public session model-selection path.
582
+
583
+ Do not render a model picker unless a future config explicitly enables it.
584
+
585
+ ---
586
+
587
+ ## 12. Browser-side plugin activation
588
+
589
+ ### 12.1 Dependencies
590
+
591
+ The browser half should depend only on public DSH services required for:
592
+
593
+ - slots;
594
+ - client runtime/sessions;
595
+ - connection state;
596
+ - theme tokens;
597
+ - settings/config projection;
598
+ - optionally locale.
599
+
600
+ Conceptual Cordis inject list:
601
+
602
+ ```ts
603
+ export const inject = [
604
+ "slots",
605
+ "sessions",
606
+ "connection",
607
+ "theme",
608
+ // settings service used for qa-surface config
609
+ ];
610
+ ```
611
+
612
+ Exact service names must be resolved against the target DSH release.
613
+
614
+ ### 12.2 Slot registration
615
+
616
+ Register one root-scoped entry into:
617
+
618
+ ```text
619
+ shell.overlay
620
+ ```
621
+
622
+ Conceptually:
623
+
624
+ ```ts
625
+ ctx.slots.inject("shell.overlay", () =>
626
+ ctx.slots.register(
627
+ {
628
+ name: "shell.overlay",
629
+ key: "dsh-qa-surface",
630
+ },
631
+ QaSurfaceEntry,
632
+ ),
633
+ );
634
+ ```
635
+
636
+ The entry remains mounted but returns `null` whenever the route is not active.
637
+
638
+ Do not register into `root`, `sidebar`, or `conversation` as the default implementation.
639
+
640
+ ---
641
+
642
+ ## 13. Route handling
643
+
644
+ Create a small `QaRouteController`.
645
+
646
+ Responsibilities:
647
+
648
+ - read `window.location.pathname`;
649
+ - normalize route prefix;
650
+ - publish `active: boolean`;
651
+ - react to browser history changes;
652
+ - react to `popstate`;
653
+ - optionally wrap `pushState`/`replaceState` only if DSH provides no public navigation event;
654
+ - restore patches on disposal.
655
+
656
+ Matching rules for default `/qa`:
657
+
658
+ ```text
659
+ /qa → active
660
+ /qa/ → active
661
+ /qa/foo → active when matchChildren=true
662
+ /qabc → inactive
663
+ / → inactive
664
+ /api/... → never intercepted
665
+ ```
666
+
667
+ ### 13.1 Important routing property
668
+
669
+ Do not register a Host exact `/qa` route just to return HTML.
670
+
671
+ DSH frontend-static already performs SPA fallback to `index.html` for unknown GET paths.
672
+
673
+ A dedicated Host route is only justified later if `/qa` needs special HTTP headers or a different document.
674
+
675
+ ---
676
+
677
+ ## 14. Full-screen overlay behavior
678
+
679
+ When QA route becomes active:
680
+
681
+ 1. render a fixed full-viewport surface above AppFrame;
682
+ 2. assign a high but locally-scoped z-index compatible with DSH overlays;
683
+ 3. cover the full viewport including the native sidebar rail;
684
+ 4. prevent background pointer interaction;
685
+ 5. lock background scrolling;
686
+ 6. ensure focus starts in the QA composer when appropriate;
687
+ 7. restore all modified document state when route deactivates or plugin disposes.
688
+
689
+ Recommended DOM marker:
690
+
691
+ ```text
692
+ body[data-dsh-qa-surface="active"]
693
+ ```
694
+
695
+ Use this marker for route-scoped global corrections only.
696
+
697
+ Do not permanently patch DSH CSS.
698
+
699
+ ### 14.1 Accessibility
700
+
701
+ The hidden native UI below the overlay must not remain meaningfully keyboard-accessible.
702
+
703
+ Preferred order:
704
+
705
+ 1. if a stable AppFrame root can be addressed through a public contract, set it inert while QA is active;
706
+ 2. otherwise implement focus containment inside the QA surface and prevent background pointer events;
707
+ 3. document any remaining limitation.
708
+
709
+ Do not depend on brittle generated CSS class names to find native UI.
710
+
711
+ ---
712
+
713
+ ## 15. Session controller abstraction
714
+
715
+ Create a plugin-owned adapter:
716
+
717
+ ```text
718
+ QaSessionController
719
+ ```
720
+
721
+ This is the only module allowed to know detailed DSH Session/Runtime method names.
722
+
723
+ UI components consume a plugin-owned interface such as:
724
+
725
+ ```ts
726
+ interface QaSessionState {
727
+ phase:
728
+ | "idle"
729
+ | "creating"
730
+ | "ready"
731
+ | "running"
732
+ | "reconnecting"
733
+ | "blocked"
734
+ | "error";
735
+
736
+ sessionId: string | null;
737
+ messages: readonly QaMessage[];
738
+ error: string | null;
739
+ canSend: boolean;
740
+ canStop: boolean;
741
+ }
742
+
743
+ interface QaSessionActions {
744
+ ensureSession(): Promise<void>;
745
+ send(text: string): Promise<void>;
746
+ stop(): Promise<void>;
747
+ reset(): Promise<void>;
748
+ retryConnection(): void;
749
+ }
750
+ ```
751
+
752
+ Benefits:
753
+
754
+ - isolates upstream DSH API changes;
755
+ - keeps React components DSH-version-agnostic;
756
+ - easy unit testing;
757
+ - avoids private component imports.
758
+
759
+ ---
760
+
761
+ ## 16. Session creation / restore algorithm
762
+
763
+ ### 16.1 `browser-persistent`
764
+
765
+ Pseudo-flow:
766
+
767
+ ```text
768
+ activate /qa
769
+
770
+
771
+ load stored session id
772
+
773
+ ├── exists ──► resolve/open through DSH runtime
774
+ │ │
775
+ │ ├── valid ─► use it
776
+ │ └── gone/forbidden ─► clear local id
777
+
778
+ └───────────────────────────────────────┐
779
+
780
+ create DSH Session
781
+
782
+
783
+ apply preset/model
784
+
785
+
786
+ persist session id
787
+
788
+
789
+ ready
790
+ ```
791
+
792
+ ### 16.2 Storage
793
+
794
+ Use a versioned key:
795
+
796
+ ```text
797
+ dsh-qa-surface:v1:<route>:session
798
+ ```
799
+
800
+ Do not persist:
801
+
802
+ - prompt text after send;
803
+ - raw credentials;
804
+ - provider secrets;
805
+ - tool results;
806
+ - transcript copies.
807
+
808
+ The DSH Session is the authoritative transcript store.
809
+
810
+ ### 16.3 Reset
811
+
812
+ Reset means:
813
+
814
+ - stop active generation if needed;
815
+ - create a fresh DSH Session using the configured workspace/preset;
816
+ - switch controller binding to it;
817
+ - replace stored session id;
818
+ - do not delete the old DSH Session automatically.
819
+
820
+ Old sessions can remain available to operators in normal DSH Web.
821
+
822
+ ---
823
+
824
+ ## 17. Prompt sending
825
+
826
+ The QA surface needs **plain text prompt submission**, not the full DSH developer composer.
827
+
828
+ Preferred implementation:
829
+
830
+ - use the public Session/client runtime prompt method;
831
+ - use Host-authoritative acceptance;
832
+ - disable duplicate sends while the submit request is unresolved;
833
+ - rely on DSH session events/snapshots for resulting user/assistant messages;
834
+ - do not optimistically fabricate an assistant message.
835
+
836
+ Conceptual flow:
837
+
838
+ ```text
839
+ QaComposer
840
+ │ send(text)
841
+
842
+ QaSessionController
843
+
844
+
845
+ public DSH Session.prompt(...)
846
+
847
+
848
+ Host Session Controller
849
+
850
+
851
+ Agent Loop
852
+
853
+ ├── LLM
854
+ ├── tools
855
+ ├── MCP
856
+ └── subagents
857
+
858
+
859
+ Session events
860
+
861
+
862
+ Client Session snapshot
863
+
864
+
865
+ QaTranscriptAdapter
866
+
867
+
868
+ QaTranscript
869
+ ```
870
+
871
+ ### 17.1 Slash commands
872
+
873
+ MVP QA input must treat normal text as text.
874
+
875
+ Do not expose the DSH slash-command menu.
876
+
877
+ If the public `Session.prompt()` path bypasses slash-command adjudication, that is desirable for the QA surface.
878
+
879
+ If the selected DSH API automatically interprets slash commands, document it and either:
880
+
881
+ - escape/disable them for QA;
882
+ - or explicitly allow them through config.
883
+
884
+ Default must favor predictable QA text behavior.
885
+
886
+ ---
887
+
888
+ ## 18. Transcript projection
889
+
890
+ Create:
891
+
892
+ ```text
893
+ QaTranscriptAdapter
894
+ ```
895
+
896
+ Input:
897
+
898
+ - public DSH Session conversation snapshot/event projection.
899
+
900
+ Output:
901
+
902
+ ```ts
903
+ type QaMessage =
904
+ | {
905
+ id: string;
906
+ role: "user";
907
+ text: string;
908
+ status: "committed";
909
+ }
910
+ | {
911
+ id: string;
912
+ role: "assistant";
913
+ text: string;
914
+ status: "streaming" | "committed" | "failed";
915
+ }
916
+ | {
917
+ id: string;
918
+ role: "system";
919
+ text: string;
920
+ status: "info" | "error";
921
+ };
922
+ ```
923
+
924
+ ### 18.1 Visible by default
925
+
926
+ Render:
927
+
928
+ - user text;
929
+ - assistant visible text;
930
+ - generation failure summary;
931
+ - plugin-owned status notices.
932
+
933
+ ### 18.2 Hidden by default
934
+
935
+ Do not render:
936
+
937
+ - reasoning chunks;
938
+ - tool call arguments;
939
+ - tool results;
940
+ - execution trajectory;
941
+ - subagent internals;
942
+ - system prompt;
943
+ - hidden metadata;
944
+ - raw event JSON;
945
+ - token usage.
946
+
947
+ The events may still exist in the Session and normal DSH operator UI.
948
+
949
+ ### 18.3 Tool activity
950
+
951
+ Optional config:
952
+
953
+ ```yaml
954
+ ui:
955
+ showToolActivity: true
956
+ ```
957
+
958
+ may display only generic state such as:
959
+
960
+ ```text
961
+ Searching knowledge…
962
+ Working…
963
+ ```
964
+
965
+ Do not expose raw tool names/arguments unless a later explicit feature is designed.
966
+
967
+ ---
968
+
969
+ ## 19. Streaming
970
+
971
+ Assistant text should update from the same Session client snapshot used by normal DSH.
972
+
973
+ Requirements:
974
+
975
+ - do not create a second SSE/WebSocket connection;
976
+ - do not parse provider streams directly;
977
+ - use existing DSH connection generation/reconnect path;
978
+ - preserve partial assistant text while running;
979
+ - on reconnect, converge to Host/session history;
980
+ - no duplicated chunks after reconnect;
981
+ - scrolling should follow the latest assistant output only while the user remains near the bottom.
982
+
983
+ ---
984
+
985
+ ## 20. Composer behavior
986
+
987
+ ### Default UX
988
+
989
+ - multiline textarea;
990
+ - Enter → send;
991
+ - Shift+Enter → newline;
992
+ - disable Send for empty/whitespace-only draft;
993
+ - while running, show Stop;
994
+ - optionally allow another prompt only after current turn finishes in MVP;
995
+ - restore draft on transport error when submission was not accepted;
996
+ - clear draft only after Host accepts the prompt.
997
+
998
+ ### Accessibility
999
+
1000
+ - textarea has explicit label/aria-label;
1001
+ - Send and Stop are real buttons;
1002
+ - busy status uses `aria-live` where appropriate;
1003
+ - keyboard behavior documented;
1004
+ - focus returns to composer after assistant completion unless the user moved focus intentionally.
1005
+
1006
+ ---
1007
+
1008
+ ## 21. Interactive tools, approvals and user questions
1009
+
1010
+ This is a critical safety/UX area.
1011
+
1012
+ DSH agents can enter states that require human interaction, for example:
1013
+
1014
+ - permission approval;
1015
+ - plan review;
1016
+ - `ask_user_question` / interactive question.
1017
+
1018
+ The QA surface must **not silently auto-approve** these states.
1019
+
1020
+ ### MVP policy
1021
+
1022
+ Default config:
1023
+
1024
+ ```yaml
1025
+ interaction:
1026
+ approvals: blocked
1027
+ questions: unsupported
1028
+ ```
1029
+
1030
+ Recommended deployment architecture:
1031
+
1032
+ - choose an agent/permission preset appropriate for the QA assistant;
1033
+ - avoid tools that unexpectedly require developer approval;
1034
+ - grant only the minimum non-interactive permissions intentionally configured by the operator.
1035
+
1036
+ If the Session enters an unsupported pending interaction state:
1037
+
1038
+ 1. stop accepting new text prompts;
1039
+ 2. display a generic message such as:
1040
+
1041
+ ```text
1042
+ This request requires an interaction that is not available in this assistant view.
1043
+ ```
1044
+
1045
+ 3. provide Reset or Retry where sensible;
1046
+ 4. keep the detailed interaction visible to an operator in normal DSH Web;
1047
+ 5. never resolve the approval automatically.
1048
+
1049
+ ### Post-MVP
1050
+
1051
+ Add dedicated compact UI for:
1052
+
1053
+ - questions/options;
1054
+ - explicit allow/deny approval;
1055
+ - plan review.
1056
+
1057
+ These should use the public DSH interaction APIs, not DOM automation against native dialogs.
1058
+
1059
+ ---
1060
+
1061
+ ## 22. Connection and reconnect states
1062
+
1063
+ Use DSH's existing browser-host connection.
1064
+
1065
+ QA UI states:
1066
+
1067
+ ### Initial connecting
1068
+
1069
+ ```text
1070
+ Connecting…
1071
+ ```
1072
+
1073
+ ### Reconnecting
1074
+
1075
+ Keep transcript on screen and show a non-blocking banner:
1076
+
1077
+ ```text
1078
+ Connection lost. Reconnecting…
1079
+ ```
1080
+
1081
+ ### Restored
1082
+
1083
+ Remove banner after Host state converges.
1084
+
1085
+ ### Fatal connection error
1086
+
1087
+ Show:
1088
+
1089
+ - concise error;
1090
+ - Retry button if the DSH connection layer supports it;
1091
+ - no raw stack trace to QA user.
1092
+
1093
+ Raw errors should still be logged for operators/development.
1094
+
1095
+ ---
1096
+
1097
+ ## 23. Error handling
1098
+
1099
+ Map internal failures to QA-safe UI errors.
1100
+
1101
+ Examples:
1102
+
1103
+ | Internal class | QA message |
1104
+ | ------------------------------ | --------------------------------------------- |
1105
+ | session creation rejected | "Unable to start a chat." |
1106
+ | prompt rejected | "Your message could not be sent." |
1107
+ | model route unavailable | "The assistant is temporarily unavailable." |
1108
+ | connection lost | "Connection lost. Reconnecting…" |
1109
+ | unsupported approval | "This request requires operator interaction." |
1110
+ | malformed persisted session id | silently clear and create a new session |
1111
+
1112
+ Never render:
1113
+
1114
+ - local filesystem paths;
1115
+ - credentials;
1116
+ - stack traces;
1117
+ - raw model/provider payloads;
1118
+ - internal tool arguments.
1119
+
1120
+ Add structured client logging behind a debug flag.
1121
+
1122
+ ---
1123
+
1124
+ ## 24. Styling
1125
+
1126
+ Follow DSH Web styling conventions where practical:
1127
+
1128
+ - CSS Modules;
1129
+ - reuse DSH semantic theme variables/tokens;
1130
+ - no Tailwind dependency;
1131
+ - no global CSS framework;
1132
+ - no hardcoded assumption that DSH runs dark-only or light-only.
1133
+
1134
+ QA surface may define local layout variables such as:
1135
+
1136
+ ```css
1137
+ .qaSurface {
1138
+ --qa-max-content-width: 900px;
1139
+ }
1140
+ ```
1141
+
1142
+ Shared color/typography values should derive from DSH theme tokens when available.
1143
+
1144
+ ### 24.1 Responsive requirements
1145
+
1146
+ Desktop:
1147
+
1148
+ - centered content column;
1149
+ - configurable max width;
1150
+ - sticky composer bottom.
1151
+
1152
+ Mobile:
1153
+
1154
+ - full width;
1155
+ - safe-area padding;
1156
+ - textarea remains visible above virtual keyboard as far as browser allows;
1157
+ - buttons remain touch-sized;
1158
+ - no horizontal scroll.
1159
+
1160
+ ---
1161
+
1162
+ ## 25. Theme behavior
1163
+
1164
+ The plugin should automatically follow DSH's resolved theme.
1165
+
1166
+ Do not independently implement another dark/light preference system in MVP.
1167
+
1168
+ Optional future config may force a branded theme, but normal DSH semantic variables should remain the base.
1169
+
1170
+ ---
1171
+
1172
+ ## 26. Security model
1173
+
1174
+ ### 26.1 Same-origin only by default
1175
+
1176
+ Preferred deployment:
1177
+
1178
+ ```text
1179
+ https://assistant.example.com/qa
1180
+ https://assistant.example.com/api/...
1181
+ ```
1182
+
1183
+ or a reverse proxy exposing DSH under the same public origin.
1184
+
1185
+ Do not implement cross-origin API calls merely for the QA page.
1186
+
1187
+ ### 26.2 Preserve DSH trust fence
1188
+
1189
+ Do not:
1190
+
1191
+ - forge Host/Origin;
1192
+ - disable browser trust globally;
1193
+ - add permissive CORS;
1194
+ - bypass signed browser-session/auth checks.
1195
+
1196
+ ### 26.3 Authentication is deployment-owned
1197
+
1198
+ `dsh-qa-surface` is primarily a **surface plugin**, not a full identity provider.
1199
+
1200
+ For remote/public deployment, place it behind the deployment's intended auth layer, for example:
1201
+
1202
+ - an existing DSH Web auth plugin;
1203
+ - reverse-proxy SSO/OIDC;
1204
+ - another explicitly configured access gateway.
1205
+
1206
+ The plugin must document that the DSH Host webserver itself is not a complete internet-facing authentication boundary.
1207
+
1208
+ ### 26.4 Embedding
1209
+
1210
+ If iframe embedding is later enabled:
1211
+
1212
+ - require explicit operator opt-in;
1213
+ - use CSP `frame-ancestors` at the reverse proxy or supported Host layer;
1214
+ - do not blindly set `*`;
1215
+ - preserve same-site/session-cookie requirements.
1216
+
1217
+ ---
1218
+
1219
+ ## 27. Operator vs QA-user separation
1220
+
1221
+ Desired deployment:
1222
+
1223
+ ```text
1224
+ / → operator/developer DSH Web
1225
+ /qa → end-user QA surface
1226
+ ```
1227
+
1228
+ Both may use the same Host/runtime and the same persisted Sessions.
1229
+
1230
+ A QA-created session should remain inspectable by an authorized operator in normal DSH Web.
1231
+
1232
+ The plugin must not try to hide the Session from the Host/operator data model.
1233
+
1234
+ ---
1235
+
1236
+ ## 28. Session naming / discoverability
1237
+
1238
+ QA sessions should be recognizable in normal DSH Web.
1239
+
1240
+ Options, in preference order:
1241
+
1242
+ 1. use an agent preset dedicated to QA;
1243
+ 2. set a title prefix when the public Session rename/title API makes this safe;
1244
+ 3. add plugin metadata only if DSH offers a public extension point.
1245
+
1246
+ Suggested title:
1247
+
1248
+ ```text
1249
+ QA: <auto-generated title>
1250
+ ```
1251
+
1252
+ Do not mutate raw Session log files directly.
1253
+
1254
+ ---
1255
+
1256
+ ## 29. Configuration UI
1257
+
1258
+ MVP does not require a custom rich settings page.
1259
+
1260
+ Preferred progression:
1261
+
1262
+ ### Phase A
1263
+
1264
+ Config through `cordis.patch.yml` / plugin config.
1265
+
1266
+ ### Phase B
1267
+
1268
+ Register the `qa-surface` settings namespace so values are manageable through normal DSH settings mechanisms.
1269
+
1270
+ ### Phase C
1271
+
1272
+ Optionally contribute a settings card under the DSH Plugins settings section.
1273
+
1274
+ Fields suitable for UI editing:
1275
+
1276
+ - enabled;
1277
+ - route path;
1278
+ - title;
1279
+ - subtitle;
1280
+ - welcome message;
1281
+ - placeholder;
1282
+ - workspace;
1283
+ - agent preset;
1284
+ - provider/model;
1285
+ - reset visibility;
1286
+ - suggested questions;
1287
+ - interaction policy.
1288
+
1289
+ Do not expose secrets in this settings namespace.
1290
+
1291
+ ---
1292
+
1293
+ ## 30. Example composition
1294
+
1295
+ Illustrative only; exact Loader syntax must match the deployment.
1296
+
1297
+ ```yaml
1298
+ - insert:
1299
+ - id: qa-surface
1300
+ name: dsh-qa-surface
1301
+ config:
1302
+ enabled: true
1303
+ route:
1304
+ path: /qa
1305
+ matchChildren: true
1306
+ branding:
1307
+ title: Internal Assistant
1308
+ welcomeMessage: Ask a question about the platform.
1309
+ placeholder: Ask a question...
1310
+ session:
1311
+ policy: browser-persistent
1312
+ workspaceId: company-knowledge
1313
+ agentPreset: qa-assistant
1314
+ ui:
1315
+ showReset: true
1316
+ showStop: true
1317
+ showToolActivity: false
1318
+ showReasoning: false
1319
+ ```
1320
+
1321
+ If DSH patch semantics replace a row's entire config rather than merge fields, example docs must explicitly warn users to provide the complete config object required by that row.
1322
+
1323
+ ---
1324
+
1325
+ ## 31. Agent preset example
1326
+
1327
+ The UI plugin should not own the assistant's domain prompt.
1328
+
1329
+ Prefer a DSH agent preset conceptually like:
1330
+
1331
+ ```text
1332
+ qa-assistant
1333
+ ├── system prompt / persona
1334
+ ├── knowledge search tool
1335
+ ├── Jira/Confluence MCP if allowed
1336
+ ├── selected skills
1337
+ ├── safe permission preset
1338
+ └── model policy
1339
+ ```
1340
+
1341
+ This makes the same assistant reusable from:
1342
+
1343
+ - `/qa`;
1344
+ - normal DSH Web;
1345
+ - automation;
1346
+ - future APIs.
1347
+
1348
+ ---
1349
+
1350
+ ## 32. Browser route lifecycle
1351
+
1352
+ Expected transitions:
1353
+
1354
+ ### `/` → `/qa`
1355
+
1356
+ 1. route store becomes active;
1357
+ 2. QA overlay mounts;
1358
+ 3. background interaction is disabled;
1359
+ 4. config loads;
1360
+ 5. session is restored/created;
1361
+ 6. transcript loads;
1362
+ 7. composer becomes available.
1363
+
1364
+ ### `/qa` → `/`
1365
+
1366
+ 1. QA overlay unmounts/returns null;
1367
+ 2. document scroll/focus modifications are restored;
1368
+ 3. background DSH UI becomes interactive again;
1369
+ 4. do not destroy the DSH Session;
1370
+ 5. do not dispose global DSH runtime services.
1371
+
1372
+ ### Browser reload on `/qa`
1373
+
1374
+ 1. frontend-static returns index;
1375
+ 2. normal DSH boot occurs;
1376
+ 3. plugin activates;
1377
+ 4. persisted QA Session is restored.
1378
+
1379
+ ---
1380
+
1381
+ ## 33. Native DSH UI interaction underneath overlay
1382
+
1383
+ Because the normal DSH app remains mounted, the plugin must avoid accidental duplicate user interaction.
1384
+
1385
+ Required:
1386
+
1387
+ - overlay captures pointer events;
1388
+ - native UI not visible;
1389
+ - focus cannot casually tab into background;
1390
+ - document shortcuts from native DSH should not unexpectedly trigger while typing in QA.
1391
+
1392
+ If DSH exposes no public API to suspend global keyboard handlers, investigate whether:
1393
+
1394
+ - stopping propagation from the QA surface is sufficient;
1395
+ - AppFrame can be made `inert` through a stable public/semantic selector;
1396
+ - a minimal route-scoped document guard is needed.
1397
+
1398
+ Do not bind to minified/generated class names.
1399
+
1400
+ ---
1401
+
1402
+ ## 34. Observability
1403
+
1404
+ The plugin should remain observable through the existing DSH/OTel stack where possible.
1405
+
1406
+ Add lightweight plugin metrics/events only if DSH exposes a suitable seam.
1407
+
1408
+ Useful dimensions:
1409
+
1410
+ - QA surface opened;
1411
+ - session created/restored;
1412
+ - prompt accepted/rejected;
1413
+ - turn completed/failed;
1414
+ - reconnect;
1415
+ - unsupported interaction encountered.
1416
+
1417
+ Do not record prompt/message content in telemetry by default.
1418
+
1419
+ Do not duplicate token/model telemetry already emitted by DSH.
1420
+
1421
+ ---
1422
+
1423
+ ## 35. Compatibility strategy
1424
+
1425
+ DSH currently warns that it is in developer preview and may make breaking changes.
1426
+
1427
+ The plugin must isolate DSH coupling.
1428
+
1429
+ ### Adapter boundaries
1430
+
1431
+ Keep these modules small:
1432
+
1433
+ ```text
1434
+ QaSessionController.ts DSH session runtime coupling
1435
+ QaTranscriptAdapter.ts DSH conversation projection coupling
1436
+ QaConfigController.ts DSH settings coupling
1437
+ QaRouteController.ts browser navigation coupling
1438
+ ```
1439
+
1440
+ UI components should depend on plugin-local types only.
1441
+
1442
+ ### Version support
1443
+
1444
+ In README maintain a matrix:
1445
+
1446
+ ```text
1447
+ Plugin version Tested DSH versions
1448
+ 0.1.x ...
1449
+ 0.2.x ...
1450
+ ```
1451
+
1452
+ At startup, fail gracefully when a required client service/slot is absent.
1453
+
1454
+ Do not silently mutate DSH internals to "repair" an unsupported version.
1455
+
1456
+ ---
1457
+
1458
+ ## 36. External client bundle compatibility
1459
+
1460
+ An external DSH browser plugin must produce the module format expected by the DSH Web client module loader.
1461
+
1462
+ The implementation agent must verify the current DSH external plugin build pattern before finalizing `tsdown.config.ts`.
1463
+
1464
+ Requirements:
1465
+
1466
+ - `./client` export resolves;
1467
+ - package declares `dsh.client`;
1468
+ - React/ReactDOM use DSH's shared runtime instances rather than bundling incompatible duplicates;
1469
+ - client plugin bundle passes DSH's module/bundle purity expectations;
1470
+ - CSS Modules are included correctly;
1471
+ - source map optional but recommended;
1472
+ - installation does not require rebuilding DSH's main frontend.
1473
+
1474
+ Use an existing external browser UI plugin as a packaging reference if necessary, but keep runtime integration based on official DSH contracts.
1475
+
1476
+ ---
1477
+
1478
+ ## 37. Testing plan
1479
+
1480
+ ### 37.1 Unit tests
1481
+
1482
+ #### Config
1483
+
1484
+ - default config validates;
1485
+ - route normalization;
1486
+ - invalid `/api` route rejected;
1487
+ - invalid session policy rejected;
1488
+ - fixed policy requires session id;
1489
+ - empty title/placeholder behavior defined.
1490
+
1491
+ #### Route controller
1492
+
1493
+ - `/qa` activates;
1494
+ - `/qa/` activates;
1495
+ - `/qa/foo` activates when configured;
1496
+ - `/qabc` does not activate;
1497
+ - popstate updates state;
1498
+ - disposal restores any wrapped history functions/listeners.
1499
+
1500
+ #### Session controller
1501
+
1502
+ - restore valid session;
1503
+ - missing session creates new;
1504
+ - forbidden/stale stored id clears storage;
1505
+ - reset creates new id;
1506
+ - send rejects empty string;
1507
+ - accepted send clears draft at correct time;
1508
+ - rejected send preserves draft;
1509
+ - stop calls public DSH cancellation path;
1510
+ - unsupported pending interaction blocks composer.
1511
+
1512
+ #### Transcript adapter
1513
+
1514
+ - user event → user QA message;
1515
+ - assistant chunks coalesce correctly;
1516
+ - reconnect replay does not duplicate text;
1517
+ - tool calls excluded;
1518
+ - reasoning excluded;
1519
+ - assistant failure projected safely.
1520
+
1521
+ ### 37.2 Component tests
1522
+
1523
+ - composer keyboard rules;
1524
+ - Stop replaces Send while running;
1525
+ - reset confirmation if desired;
1526
+ - reconnect banner;
1527
+ - Markdown rendering;
1528
+ - scroll-follow behavior;
1529
+ - accessibility labels.
1530
+
1531
+ ### 37.3 DSH integration tests
1532
+
1533
+ Boot actual DSH Web with plugin mounted.
1534
+
1535
+ Verify:
1536
+
1537
+ 1. `/` still shows normal DSH UI;
1538
+ 2. `/qa` returns 200 through SPA fallback;
1539
+ 3. plugin client bundle appears in DSH client module graph;
1540
+ 4. `/qa` renders full-screen QA UI;
1541
+ 5. sending text creates/uses a real DSH Session;
1542
+ 6. assistant streaming works;
1543
+ 7. tool execution can occur without tool details becoming visible;
1544
+ 8. reload restores the same session under browser-persistent policy;
1545
+ 9. normal DSH UI can inspect the QA-created session;
1546
+ 10. `/qa` does not open a second server/connection stack.
1547
+
1548
+ ### 37.4 Security tests
1549
+
1550
+ - route does not weaken `/api` trust checks;
1551
+ - config endpoint/settings do not leak secrets;
1552
+ - raw tool result not visible;
1553
+ - stack traces not visible;
1554
+ - unsupported approval not auto-approved;
1555
+ - cross-origin behavior remains blocked unless deployment explicitly changes it.
1556
+
1557
+ ### 37.5 Mobile tests
1558
+
1559
+ At minimum test:
1560
+
1561
+ - 375px width;
1562
+ - 430px width;
1563
+ - landscape small viewport;
1564
+ - virtual keyboard/composer visibility manually.
1565
+
1566
+ ---
1567
+
1568
+ ## 38. Acceptance criteria
1569
+
1570
+ MVP is complete when all are true.
1571
+
1572
+ ### Routing
1573
+
1574
+ - [ ] `GET /qa` loads successfully using the normal DSH Web server.
1575
+ - [ ] `/` remains unchanged.
1576
+ - [ ] `/qa` shows no visible normal DSH navigation/settings UI.
1577
+ - [ ] leaving `/qa` restores normal UI without reload when SPA navigation is used.
1578
+
1579
+ ### Runtime
1580
+
1581
+ - [ ] QA messages are sent through a real DSH Session.
1582
+ - [ ] normal DSH tools/skills/MCP remain usable by the configured agent.
1583
+ - [ ] no direct provider API call exists in the plugin.
1584
+ - [ ] transcript persists through DSH.
1585
+ - [ ] browser-persistent session survives reload.
1586
+
1587
+ ### UI
1588
+
1589
+ - [ ] streaming assistant text works.
1590
+ - [ ] Send works.
1591
+ - [ ] Stop works.
1592
+ - [ ] Reset works.
1593
+ - [ ] Markdown works.
1594
+ - [ ] mobile layout is usable.
1595
+ - [ ] tool calls/reasoning are hidden by default.
1596
+
1597
+ ### Safety/security
1598
+
1599
+ - [ ] no auto-approval.
1600
+ - [ ] no raw internal errors in QA UI.
1601
+ - [ ] no trust-fence bypass.
1602
+ - [ ] no permissive CORS added.
1603
+ - [ ] no secrets stored in localStorage.
1604
+
1605
+ ### Maintainability
1606
+
1607
+ - [ ] no DSH core patch required.
1608
+ - [ ] no DSH frontend rebuild required for plugin installation.
1609
+ - [ ] DSH-specific API coupling isolated behind adapters.
1610
+ - [ ] compatibility matrix documented.
1611
+
1612
+ ---
1613
+
1614
+ ## 39. Implementation phases
1615
+
1616
+ ### Phase 0 — Compatibility spike
1617
+
1618
+ Goal: prove the external browser plugin packaging and public APIs before building UI.
1619
+
1620
+ Tasks:
1621
+
1622
+ 1. create package skeleton;
1623
+ 2. build Host and `./client` halves;
1624
+ 3. mount plugin through DSH config;
1625
+ 4. register trivial `shell.overlay` entry;
1626
+ 5. prove it appears only on `/qa`;
1627
+ 6. inspect available public client runtime Session APIs;
1628
+ 7. prove one session can be created/restored;
1629
+ 8. prove one plain-text prompt can be sent;
1630
+ 9. prove assistant text can be observed from a public snapshot;
1631
+ 10. record exact tested DSH commit/version in `docs/COMPATIBILITY.md`.
1632
+
1633
+ **Exit condition:** a hardcoded QA overlay can send `hello` through DSH and render the assistant reply without importing private components.
1634
+
1635
+ ### Phase 1 — MVP surface
1636
+
1637
+ Tasks:
1638
+
1639
+ 1. route controller;
1640
+ 2. full-screen shell.overlay surface;
1641
+ 3. QA layout/components;
1642
+ 4. session controller;
1643
+ 5. transcript adapter;
1644
+ 6. plain text composer;
1645
+ 7. streaming;
1646
+ 8. stop;
1647
+ 9. reset;
1648
+ 10. browser-persistent session id;
1649
+ 11. errors/reconnect;
1650
+ 12. Markdown;
1651
+ 13. basic responsive CSS.
1652
+
1653
+ ### Phase 2 — Configuration
1654
+
1655
+ Tasks:
1656
+
1657
+ 1. Schemastery config;
1658
+ 2. settings namespace;
1659
+ 3. browser config controller;
1660
+ 4. branding values;
1661
+ 5. workspace binding;
1662
+ 6. preset/model selection;
1663
+ 7. suggested questions;
1664
+ 8. README config examples.
1665
+
1666
+ ### Phase 3 — Hardening
1667
+
1668
+ Tasks:
1669
+
1670
+ 1. focus containment/background inert behavior;
1671
+ 2. unsupported interaction handling;
1672
+ 3. sanitization/error mapping;
1673
+ 4. reconnect/replay tests;
1674
+ 5. mobile fixes;
1675
+ 6. accessibility pass;
1676
+ 7. security test suite;
1677
+ 8. compatibility guards.
1678
+
1679
+ ### Phase 4 — Deployment/embedding
1680
+
1681
+ Tasks:
1682
+
1683
+ 1. Docker/local-plugin installation docs;
1684
+ 2. reverse-proxy example;
1685
+ 3. auth prerequisite docs;
1686
+ 4. iframe notes;
1687
+ 5. CSP/frame-ancestor guidance;
1688
+ 6. production smoke test.
1689
+
1690
+ ### Phase 5 — Optional native QA profile
1691
+
1692
+ Only after plugin MVP is stable.
1693
+
1694
+ Create a separate bundle/profile that composes:
1695
+
1696
+ ```text
1697
+ base runtime
1698
+ + webserver
1699
+ + connection/API
1700
+ + client runtime
1701
+ + theme
1702
+ + qa surface
1703
+ ```
1704
+
1705
+ and omits unnecessary standard DSH UI packages.
1706
+
1707
+ The existing `dsh-qa-surface` UI/controller code should be reusable without substantial rewrite.
1708
+
1709
+ ---
1710
+
1711
+ ## 40. Coding-agent instructions
1712
+
1713
+ The implementation agent should follow these rules.
1714
+
1715
+ 1. **Read current upstream source first.** DSH is moving quickly; verify all slot/service/method names against the installed version.
1716
+ 2. **Do not deep-import private DSH React components.** If a needed behavior is not exposed publicly, implement a plugin-local presentation over the public object layer.
1717
+ 3. **Do not patch DSH core for MVP.** Escalate instead if a true public extension point is missing.
1718
+ 4. **Do not claim root/sidebar/conversation single slots globally.** MVP surface is additive `shell.overlay`.
1719
+ 5. **Use the normal DSH Session Controller/client runtime.** No direct provider requests.
1720
+ 6. **Treat Host state as authoritative.** Client-local state is UI/draft/session-id reference only.
1721
+ 7. **Never auto-approve an interaction.** Unsupported interaction blocks safely.
1722
+ 8. **Keep DSH-specific APIs behind adapter modules.**
1723
+ 9. **Add tests together with each adapter.**
1724
+ 10. **Record every upstream contract relied on in `docs/COMPATIBILITY.md`.**
1725
+ 11. **Prefer same-origin.** Do not solve embedding by weakening Host/Origin checks.
1726
+ 12. **Keep configuration declarative.** Support Docker/self-hosted deployment without manual post-update patching.
1727
+
1728
+ ---
1729
+
1730
+ ## 41. Suggested first implementation commits
1731
+
1732
+ Recommended sequence:
1733
+
1734
+ ```text
1735
+ 1. chore: bootstrap dsh-qa-surface dual-face package
1736
+ 2. feat: register route-aware shell.overlay entry
1737
+ 3. feat: add qa full-screen layout and theme integration
1738
+ 4. feat: add DSH session adapter and session restore/create
1739
+ 5. feat: add transcript adapter and streaming assistant messages
1740
+ 6. feat: add composer send/stop/reset flows
1741
+ 7. feat: add plugin settings/config projection
1742
+ 8. feat: add unsupported-interaction and reconnect states
1743
+ 9. test: add DSH integration and route/session coverage
1744
+ 10. docs: add install, configuration, security and compatibility docs
1745
+ ```
1746
+
1747
+ ---
1748
+
1749
+ ## 42. Known risks
1750
+
1751
+ ### Risk: upstream DSH client contracts change
1752
+
1753
+ Mitigation:
1754
+
1755
+ - adapter boundary;
1756
+ - compatibility matrix;
1757
+ - integration CI against pinned DSH version(s).
1758
+
1759
+ ### Risk: `shell.overlay` is semantically a floating layer
1760
+
1761
+ It is currently the safest additive frame-wide seat, but it is not a true global-page primitive.
1762
+
1763
+ Mitigation:
1764
+
1765
+ - treat it as MVP transport only;
1766
+ - keep QA component independent of overlay mechanics;
1767
+ - future migration to dedicated `qa` profile/global-page extension requires changing only the mount adapter.
1768
+
1769
+ ### Risk: background DSH UI still exists
1770
+
1771
+ Mitigation:
1772
+
1773
+ - full viewport pointer capture;
1774
+ - focus containment/inert where stable;
1775
+ - route-scoped keyboard handling tests.
1776
+
1777
+ ### Risk: hidden interactive approval blocks the agent
1778
+
1779
+ Mitigation:
1780
+
1781
+ - QA-specific permission preset;
1782
+ - explicit pending-interaction detection;
1783
+ - fail closed;
1784
+ - later implement real compact interaction UI.
1785
+
1786
+ ### Risk: multi-user use with browser-persistent sessions
1787
+
1788
+ Browser-local session mapping is not an identity system.
1789
+
1790
+ Mitigation:
1791
+
1792
+ - MVP intended for authenticated/internal deployments;
1793
+ - later add per-user mapping only when a stable authenticated user identity is available.
1794
+
1795
+ ### Risk: workspace coupling
1796
+
1797
+ QA agent may accidentally gain access to an overly broad workspace.
1798
+
1799
+ Mitigation:
1800
+
1801
+ - configure a dedicated QA workspace/preset;
1802
+ - minimum permissions;
1803
+ - do not automatically choose arbitrary workspaces.
1804
+
1805
+ ---
1806
+
1807
+ ## 43. Future evolution
1808
+
1809
+ Potential roadmap:
1810
+
1811
+ ```text
1812
+ 0.1 full-screen /qa overlay + one DSH session
1813
+ 0.2 settings + branding + suggested questions
1814
+ 0.3 user questions / approval UI
1815
+ 0.4 attachments + citations + feedback
1816
+ 0.5 iframe/embed SDK
1817
+ 1.0 dedicated qa profile/bundle option
1818
+ ```
1819
+
1820
+ Long term the project can become a reusable **DSH-powered assistant frontend** rather than merely an alternative theme.
1821
+
1822
+ Possible future modes:
1823
+
1824
+ ```text
1825
+ /qa
1826
+ /qa/<assistant-id>
1827
+ /embed/qa/<assistant-id>
1828
+ ```
1829
+
1830
+ Each assistant id could map to a configured DSH agent preset, while the underlying runtime remains shared.
1831
+
1832
+ ---
1833
+
1834
+ ## 44. Research references
1835
+
1836
+ Current architecture was checked against upstream DeepSeek Harness sources/docs on 2026-09-05.
1837
+
1838
+ Key references:
1839
+
1840
+ - DeepSeek Harness repository / developer-preview status
1841
+ https://github.com/deepseek-ai/deepseek-harness
1842
+
1843
+ - Web app bundle composition (`dsh-base` + browser roster)
1844
+ https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/bundle/web-app/cordis.patch.yml
1845
+
1846
+ - Web app bundle README
1847
+ https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/bundle/web-app/README.md
1848
+
1849
+ - Client modules / `dsh.client` / `window.__DSH_BOOT__`
1850
+ https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/subsystems/client-modules.md
1851
+
1852
+ - Host webserver route/fallback model
1853
+ https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/host/webserver/README.md
1854
+
1855
+ - SPA frontend-static fallback
1856
+ https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/host/frontend-static/README.md
1857
+
1858
+ - Client runtime / Host-born sessions
1859
+ https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/runtime/README.md
1860
+
1861
+ - Session Controller
1862
+ https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/api/session-controller/README.md
1863
+
1864
+ - Web client slots
1865
+ https://deepseek-harness.github.io/deepseek-harness/en/reference/subsystems/slots
1866
+
1867
+ - Conversation slot contract / `conversation.session`
1868
+ https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-conversation/src/client/contract/slots.ts
1869
+
1870
+ - UI layout shell / `shell.overlay`
1871
+ https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-layout/README.md
1872
+
1873
+ - Web client package rules / new UI plugin checklist
1874
+ https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/AGENTS.md
1875
+
1876
+ - Settings-card cookbook / external dual-face plugin pattern
1877
+ https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/cookbook/adding-a-settings-card.md
1878
+
1879
+ ### Upstream limitation worth tracking
1880
+
1881
+ At the time of writing there is no generic first-class global-page extension contract in the normal DSH shell; community discussion exists around this missing surface. The plugin should therefore keep its QA UI mount mechanism replaceable so it can migrate from `shell.overlay` if DSH later adds a proper global-page/work-area API.
1882
+
1883
+ ---
1884
+
1885
+ ## 45. Final architecture summary
1886
+
1887
+ ```text
1888
+ ┌──────────────────────────────┐
1889
+ │ DeepSeek Harness │
1890
+ │ │
1891
+ │ Agent / Session / Tools │
1892
+ │ Skills / MCP / Models │
1893
+ │ Memory / Persistence / OTel │
1894
+ └──────────────┬───────────────┘
1895
+
1896
+ Session Controller
1897
+
1898
+ API / connection
1899
+
1900
+ ┌──────────────────────┴──────────────────────┐
1901
+ │ │
1902
+ Normal DSH Web /qa route
1903
+ │ │
1904
+ standard AppFrame dsh-qa-surface client
1905
+ │ │
1906
+ sidebar/conversation shell.overlay (full)
1907
+ settings/tools/etc. │
1908
+ QaSessionController
1909
+
1910
+ QaTranscriptAdapter
1911
+
1912
+ QA React UI
1913
+
1914
+ textarea / Send
1915
+ ```
1916
+
1917
+ The core architectural principle is:
1918
+
1919
+ > **Replace the presentation, not the harness.**
1920
+
1921
+ `dsh-qa-surface` should be a thin QA/browser surface over the existing DSH runtime, with `/qa` acting as a dedicated end-user presentation while `/` remains the full operator/developer interface.