Files
deepseek-harness/.agents/notes/implemented/architecture/2026-10-20-new-session-blank-reuse-by-preset.md
T
5ff29190ed feat(ui): add navigation entry customization (#6247)
* fix(ui): avoid tooltip trigger overlap when a side fits

* feat(ui): add navigation entry customization

* fix(test): keep tooltip geometry independent of JSX types

* fix(ui): align customization catalogs and integration fixtures

* test(ui): complete customization lifecycle and release integration coverage

* fix(ui): return entry discovery to the installed plugin list

* docs: refresh module graph for entry customization

* build: refresh workspace links for the moved entry customization package

* docs: regenerate catalogs and pairing hashes for the moved entry customization package

* test(web): cover default-loaded entry customization in the Web e2e

* test(ui): describe entry customization as a browser plugin in the host entry spec

* build: order entry customization publication files as workspace constraints require

* test(release): drop the moved entry customization package from the experimental family list

* style(ui): use the shared focus ring color on entry customization triggers

* test(web): re-record goldens for the default-loaded More navigation menus

* test(web): add the More conversation views trigger to the remaining CI-reported goldens

* docs: record region-owned pins for plugin entry customization

* refactor(ui): rebuild entry customization on region-owned pins and More menus

- Each navigation region (left sidebar, Conversation header, right sidebar
  guide) persists its own unpinned list under
  dsh.slot.manual-order.<list slot key>.v1, filters its own launchers, and
  draws its own More button and Menu around its actions slot.
- ui-entry-customization only registers the shared menu rows (entries with
  pin buttons, Browse other plugins, Create your plugin) into the three
  actions slots and keeps no state.
- Remove ctx.layout.entryVisibility, the plugin store, left-sidebar drag
  reordering, and agentPresetNavigation; ui-layout and ui-agent-preset match
  release/dsh-0.2.1 again.
- Keep the release <nav> and Conversation tab-bar visibility conditions.
- Move the More glyph into ui-primitives as IconAppsAddOutline{Regular,Medium}.

* docs: compile the entry customization owner props example

* docs(ui): describe the wrapping Conversation tab row in the header comment

* fix(ui): return New Session to the Workspace blank and its unsent draft

- New Session (startSession without options) takes the blank-reuse path
  again instead of creating a fresh Session on every call, which hid the
  previous blank and its unsent draft since a5c9c2aaeef (#5775).
- A request that names an Agent preset skips blanks listing a different
  preset, so Creator entries no longer take over an ordinary draft.
- Record the change in a v0.2.1-rc.2 upgrade guide; the Host refresh of a
  stale loaded blank stays deferred (see the Agent Note).

* docs: regenerate the Cordis inspect catalog for New Session blank reuse

* fix(ui): keep the selected entry visible while unpinned

- Left-sidebar rows and View tabs keep the selected entry, with aria-current or aria-selected, until another entry is selected.
- Entry menu Creator action takes no region argument; the registration's region selects the prompt.
- Document the 2px panel-row and 32px View-tab gaps; drop commit hashes from the New Session Agent Note.

* fix(ui): leave a blank of another preset unstaged when starting Creator

startCreatorDraft stages cordis only when the main blank lists no preset or already lists cordis; navigation opens a separate Creator blank otherwise, so a Standard draft blank keeps its preset.

* feat(ui): choose whether a draft prompt replaces, appends to, or keeps an existing draft

- DraftInitializationOptions gains whenDraftExists ('replace' | 'append' | 'keep', default 'keep'); clearPreviousDraft stays and maps to 'replace' only when whenDraftExists is omitted.
- Append adds the prompt as new paragraphs after the existing text and references; attachments stay in every mode.
- The three Create your plugin entries append their regional prompt.

* test(web): align e2e with New Session blank reuse and the visible selected entry

* refactor(ui): share the draft paragraph writer between setDraft and appendDraft

---------

Co-authored-by: Yif <[email protected]>
Co-authored-by: imccyu <[email protected]>
2026-10-20 15:17:13 +08:00

5.5 KiB

Agent Note: New Session reuses a blank Session, matching a requested preset

Status: implemented

English | 中文

Problem

In 0.2.0-rc.2, New Session (the sidebar button, the Workspace "+" button, and the shortcut, all calling uiWorkspace.startSession without options) reused the Workspace's first blank Session. PR #5775, first released in dsh-v0.2.1-alpha.2, changed it to create a fresh Session and Agent on every call, so that New Session composes the current preset definition after the user changes one instead of keeping a reused blank's earlier composition (upgrade guide).

A blank Session that is not current is hidden from the Session tree, and New Session is the only way back to it. After that change, a user who types a draft into a blank Session, switches to another Session, and clicks New Session lands in another empty Session; the original draft stays in localStorage under the old Session id, but no control returns to it. 0.2.1-rc.2 therefore loses New Session's unsent drafts.

The Creator entries (startSession(…, { agentPreset: 'cordis', prompt, clearPreviousDraft: false })) also reuse the first blank Session and switch it to Creator: an existing ordinary draft stays under Creator mode and the Creator prompt is not filled in, and New Session can likewise take over a Creator blank.

Decision

Only the Client (ui-workspace) changes; the Host is unchanged:

  • startSession without options takes the reuse path again and follows the same rule as openWorkspace, connectWorkspace, and startSession with options.
  • createInWorkspace names the first unstarted, unarchived blank Session that is still a Workspace member. A request that names a preset skips a blank whose listed preset (projectionValues.agentPreset) is a different one, while a blank with no listed preset remains eligible; a request without a preset may name any blank, which the Host switches to its default preset. The Host creates a Session when no blank is eligible.

As a result, New Session returns to the original blank Session with its unsent draft, and preset flows such as the Creator entries do not take over a blank that lists another preset, so an ordinary draft is not moved under Creator. No persisted state is added, and the Client does not read the preset roster.

Alternatives considered

Revert the PR #5775 change completely, reusing the first blank for every request. Drafts come back, but preset flows such as the Creator entries take over the blank holding the user's ordinary draft, switch it to Creator, keep the draft there, and cannot fill in their prompt.

Keep creating a fresh Session and move the abandoned blank's draft into it. This needs rules for which blank to move from when several exist, how to treat attachments, and whether to archive the old Session, which is more change and state than reusing blanks.

Let a request without a preset reuse only a blank of the Host default preset. The Client does not know the default preset and would have to request the agentPresets.list() roster.

Record a persisted source-to-blank-Session map in the Client. It duplicates the preset each blank already records and must handle stale entries and per-origin isolation.

Clear the existing draft in place when the preset changes (clearPreviousDraft: true). It deletes a draft the user was writing in the other mode.

Also change the Host to always call presets.select when reusing a loaded blank. It fully keeps #5775's goal but is outside this Client-only fix; see Deferred work.

Verification

  • ui-workspace unit tests: New Session without options returns to the existing blank; a request without a preset reuses any blank, a request with a preset reuses only a blank listing the same preset, and a new Session is created when none matches. navigation.ts has 100% per-file coverage.
  • Regression localization: the same unit probe on the PR #5775 commit that made this change and on its parent, plus a browser reproduction on builds of both, confirmed that the draft loss comes from that change.

Consequences

  • New Session no longer guarantees the current preset definition: a reused blank whose Agent is still loaded in the Host with the same preset id keeps its creation-time composition; a blank resumed after a Host restart composes the current definition.
  • New Session may reuse a blank left by a preset flow such as the Creator entries; the Host switches it back to the default preset and its draft stays.
  • The new-session-blank-reuse upgrade guide records the change relative to 0.2.1-rc.2.

Deferred work

  • Host reuseLive (packages/api/session-controller/src/agent.ts) returns the loaded Agent unchanged when the preset id matches and does not call presets.select. select decides whether a definition was registered again by comparing Generation objects: when they are the same it does nothing; when they differ it recomposes under the same Session id with the current definition and records agent-preset/selected; on a started Session it throws agent-preset/locked, and the caller creates a new Session instead. Removing the target === current early return and always calling select keeps drafts while meeting #5775's goal that New Session composes the current definition; it needs a Host test, and updates to the reuseLive JSDoc and to the upgrade guide's statement that the current preset definition is no longer guaranteed.