feat: implement new session behavior to clear selection and show empty state
- Added bilingual notes for the new session feature, detailing the transition to an empty state upon session creation. - Updated `SessionsService` to include a `clear()` method that resets the current selection and persists the empty state. - Enhanced the `EmptyState` component to reflect the new design, including workspace selection and input handling. - Modified CSS styles for improved layout and visual consistency in the empty state. - Updated tests to cover the new session clearing functionality and its effects on the UI.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# @deepseek-ai/dsh-client-ui-sidebar
|
||||
|
||||
Sidebar plugin: session multi-level tree (cwd grouping + parentId nesting), search, by-workspace grouping, state dots, three creation entries. Collapse is a slide + crossfade into the layout-owned 56px rail (open / new session / new workspace / search — search expands and focuses the search box — plus the settings foot): the expanded content freezes at its width and fades in place while the column slides over it, then the rail — whale mark resting, panel icon on hover, tooltips on every control — crossfades in at settle as the wide content unmounts. Contract: the [slot system standard](../../../.agents/notes/implemented/architecture/2026-07-22-slot-type-chain-implementation.md).
|
||||
Sidebar plugin: session multi-level tree (cwd grouping + parentId nesting), search, by-workspace grouping, state dots, three creation entries. Top-level New Session / New Workspace clear the selection onto `conversation.empty`; per-project "+" still create-then-opens. Collapse is a slide + crossfade into the layout-owned 56px rail (open / new session / new workspace / search — search expands and focuses the search box — plus the settings foot): the expanded content freezes at its width and fades in place while the column slides over it, then the rail — whale mark resting, panel icon on hover, tooltips on every control — crossfades in at settle as the wide content unmounts. Contract: the [slot system standard](../../../.agents/notes/implemented/architecture/2026-07-22-slot-type-chain-implementation.md).
|
||||
|
||||
`src/client/contract/slots.ts` is the single-domain contract file: `SidebarRootInjected` (the registrant's own injected share — plain service callbacks: onOpen/onCreate/onToggleSidebar) and `SidebarRootComponentProps = PropsRuntime<'sidebar'> & SidebarRootInjected` (owner `{collapsed,width}` plus the standard `useSessions` hook, resolved off ui-layout's SlotMap declaration, never re-stated). `apply` registers SidebarRoot cast-free against that composition; the inject factory closes over the plugin's own ctx.
|
||||
|
||||
|
||||
@@ -25,8 +25,8 @@ export type SidebarRootInjected = {
|
||||
/** Open (switch to) a session. */
|
||||
onOpen: (id: SessionId) => void
|
||||
/**
|
||||
* Create a session and open it; cwd targets a project group (the
|
||||
* sidebar's three creation entries all land in the new session).
|
||||
* New-session affordance: no cwd clears selection onto the empty-state
|
||||
* launch; a cwd create-then-opens a session in that project group.
|
||||
*/
|
||||
onCreate: (cwd?: string) => void
|
||||
/** Collapse the sidebar column (layout service action; owner share stays {collapsed,width}). */
|
||||
|
||||
@@ -27,9 +27,15 @@ export function apply(ctx: ClientContext): void {
|
||||
// list snapshot); layout keeps only panel geometry.
|
||||
onOpen: (id) => { ctx.sessions.open(id) },
|
||||
onCreate: (cwd) => {
|
||||
// Create-then-open: the sidebar's three creation entries all land
|
||||
// in the new session (empty-state first-send stays with ui-conversation).
|
||||
void ctx.sessions.create(cwd === undefined ? {} : { cwd })
|
||||
// Top-level New Session / New Workspace: clear selection so AppFrame
|
||||
// shows conversation.empty (EmptyState + shared InputBar). Per-project
|
||||
// "+" still create-then-opens into that cwd until workspace seeding
|
||||
// reaches the empty-state picker.
|
||||
if (cwd === undefined) {
|
||||
ctx.sessions.clear()
|
||||
return
|
||||
}
|
||||
void ctx.sessions.create({ cwd })
|
||||
.then((id: SessionId) => { ctx.sessions.open(id) })
|
||||
},
|
||||
onToggleSidebar: () => { ctx.layout.toggleSidebar() },
|
||||
|
||||
@@ -26,7 +26,12 @@ async function bench() {
|
||||
byId: { [sid('a')]: { id: sid('a'), title: 'alpha', displayTitle: 'alpha', cwd: '/proj', running: false, updatedAt: 1 } },
|
||||
current: undefined,
|
||||
})
|
||||
const sessions = { list, create: vi.fn(async () => sid('minted')), open: vi.fn() }
|
||||
const sessions = {
|
||||
list,
|
||||
create: vi.fn(async () => sid('minted')),
|
||||
open: vi.fn(),
|
||||
clear: vi.fn(),
|
||||
}
|
||||
const layout = { toggleSidebar: vi.fn() }
|
||||
ctx.provide('sessions', sessions)
|
||||
ctx.provide('layout', layout)
|
||||
@@ -91,14 +96,15 @@ describe('apply', () => {
|
||||
expect(sessions.open).toHaveBeenCalledWith('a')
|
||||
|
||||
injected.onCreate()
|
||||
expect(sessions.create).toHaveBeenCalledWith({})
|
||||
expect(sessions.clear).toHaveBeenCalledOnce()
|
||||
expect(sessions.create).not.toHaveBeenCalled()
|
||||
|
||||
injected.onCreate('/proj')
|
||||
expect(sessions.create).toHaveBeenCalledWith({ cwd: '/proj' })
|
||||
// create-then-open lands after the create promise resolves.
|
||||
await Promise.resolve()
|
||||
await Promise.resolve()
|
||||
expect(sessions.open).toHaveBeenCalledWith('minted')
|
||||
|
||||
injected.onCreate('/proj')
|
||||
expect(sessions.create).toHaveBeenCalledWith({ cwd: '/proj' })
|
||||
})
|
||||
|
||||
it('teardown unregisters the slot entry', async () => {
|
||||
|
||||
Reference in New Issue
Block a user