fix: keep the notes list accessible in narrow workspaces

Default the notes list to 200px with a 160px minimum and hide it automatically only below 600px. Keep the detail toolbar toggle available to open a drawer, with dismissal and note-switching support. Restore reading positions after rich text content effects populate the record.

Include the existing document-table and file-tree scroll anchoring styles in this file-level commit. Validation: 143 related tests, Electron layout regression, typecheck and changed-file lint passed; repository lint is blocked by unrelated App.tsx changes.

Release note: The notes list takes less space and remains accessible through a drawer in narrow windows. Existing saved list widths are preserved.
This commit is contained in:
mesalogo
2026-10-03 16:02:27 +08:00
parent 10065da8ac
commit a620c22676
6 changed files with 169 additions and 34 deletions
+15
View File
@@ -1,5 +1,20 @@
# Magic Notes Progress
## 2026-10-03: Compact Notes List And Narrow Drawer
The notes list now defaults to 200px with a 160px minimum. At workspace widths
of 600px or more it can remain beside detail; below 600px the toolbar toggle
opens the mounted list as an overlay drawer. Escape, outside click, the close
button and note selection dismiss it. Existing desktop width preferences remain
intact. Scroll restoration now runs after read-only content effects populate the
record, fixing a failure exposed by the Electron layout regression.
Validation: 143 workspace and list-window tests passed, along with the real
Electron layout test covering native resizing, drawer geometry, Escape focus
return and note switching. Typecheck and lint on changed code passed. Repository
lint was blocked by concurrent App.tsx changes (max-lines error and a missing
conversationStore hook dependency warning). No provider calls were made.
## 2026-10-01: Header New Note Action
New note is now one primary page-header button that always follows the view
+12 -6
View File
@@ -21,13 +21,17 @@ stream events, error causes and nested release/dispose cleanup.
## Continuous Record Workspace
The outer workspace separates the persistent notes list from the detail container.
The list defaults to 280px, resizes within 240-420px and stores `notesPaneWidth`
and `notesPaneOpen` in `goodbuddy.magic-notes-layout.v1`. At workspace widths up to
900px, selection replaces the visible list with detail while retaining the mounted
list. The right-hand `magic-notes-detail` query container and its ResizeObserver
The list defaults to 200px, resizes within 160-420px and stores `notesPaneWidth`
and `notesPaneOpen` in `goodbuddy.magic-notes-layout.v1`. Existing saved widths are
preserved. Below 600px, selection replaces the visible list with detail while
retaining the mounted list. The detail toolbar keeps its list toggle, opening an
overlay drawer without shrinking detail. Selecting a note, clicking outside,
using the drawer close button or pressing Escape closes the drawer; dismissal
returns focus to the toggle. Drawer state is transient and separate from the
desktop open preference. The right-hand `magic-notes-detail` query container and its ResizeObserver
measure the detail width for two breakpoints: at 800px or less the record index
becomes a drawer while AI stays beside the editor; at 600px or less AI stacks
below the stream. The 900px list/detail switch is driven by the same measured
below the stream. The 600px list/detail switch is driven by the same measured
workspace width in both CSS class and React state, so the two cannot disagree.
Per-note stream scroll positions live in a workspace-local map; explicit entry
navigation takes precedence over reading-position restoration.
@@ -405,7 +409,9 @@ The overview's `libraryView` and the optional note-only `detailView` are indepen
opening a todo's source note does not lose the originating task view.
The notes list remains visible beside detail on desktop unless collapsed. On
narrow screens, or when opening a source note from To-dos, the originating list
remains mounted but hidden, retaining scroll position and filters. Returning restores the initiating control's focus and
remains mounted but hidden, retaining scroll position and filters. The narrow
notes view can reopen that list through the detail toolbar drawer toggle.
Returning restores the initiating control's focus and
invalidates outstanding detail requests. Initial loading fetches summaries and
todos without selecting the first note or task. Navigation away from unsaved content or
an unsaved title uses the existing draft confirmation; active writes finish
+38 -4
View File
@@ -610,13 +610,13 @@ describe('MagicNotesWorkspace overview navigation', () => {
const view = render(<MagicNotesWorkspace onNotify={onNotify} />)
await openNote()
const separator = screen.getByRole('separator', { name: '调整笔记列表宽度' })
expect(separator).toHaveAttribute('aria-valuenow', '280')
expect(separator).toHaveAttribute('aria-valuenow', '200')
fireEvent.keyDown(separator, { key: 'ArrowRight' })
expect(separator).toHaveAttribute('aria-valuenow', '296')
expect(separator).toHaveAttribute('aria-valuenow', '216')
fireEvent.keyDown(separator, { key: 'End' })
expect(separator).toHaveAttribute('aria-valuenow', '420')
fireEvent.keyDown(separator, { key: 'Home' })
expect(separator).toHaveAttribute('aria-valuenow', '240')
expect(separator).toHaveAttribute('aria-valuenow', '160')
fireEvent.click(screen.getByRole('button', { name: '收起笔记列表' }))
expect(screen.queryByRole('searchbox')).not.toBeInTheDocument()
expect(screen.getByRole('region', { name: '笔记记录' })).toBeVisible()
@@ -626,7 +626,41 @@ describe('MagicNotesWorkspace overview navigation', () => {
await openNote()
expect(screen.getByRole('button', { name: '展开笔记列表' })).toHaveAttribute('aria-expanded', 'false')
fireEvent.click(screen.getByRole('button', { name: '展开笔记列表' }))
expect(screen.getByRole('separator', { name: '调整笔记列表宽度' })).toHaveAttribute('aria-valuenow', '240')
expect(screen.getByRole('separator', { name: '调整笔记列表宽度' })).toHaveAttribute('aria-valuenow', '160')
})
it.each([600, 700])('keeps the notes list beside detail at %ipx', async (width) => {
vi.spyOn(window, 'innerWidth', 'get').mockReturnValue(width)
render(<MagicNotesWorkspace onNotify={onNotify} />)
await openNote()
expect(screen.getByRole('region', { name: '笔记列表' })).toBeVisible()
expect(screen.getByRole('separator', { name: '调整笔记列表宽度' })).toBeVisible()
})
it('opens the notes drawer below 600px and dismisses or switches notes', async () => {
vi.spyOn(window, 'innerWidth', 'get').mockReturnValue(599)
render(<MagicNotesWorkspace onNotify={onNotify} />)
await openNote()
expect(screen.queryByRole('region', { name: '笔记列表' })).not.toBeInTheDocument()
const toggle = screen.getByRole('button', { name: '展开笔记列表' })
fireEvent.click(toggle)
const sidebar = screen.getByRole('region', { name: '笔记列表' })
expect(sidebar).toBeVisible()
expect(screen.getByRole('searchbox')).toHaveFocus()
expect(screen.queryByRole('separator', { name: '调整笔记列表宽度' })).not.toBeInTheDocument()
fireEvent.keyDown(screen.getByRole('searchbox'), { key: 'Escape' })
expect(toggle).toHaveFocus()
expect(toggle).toHaveAttribute('aria-expanded', 'false')
fireEvent.click(toggle)
fireEvent.click(document.querySelector('.magic-notes-list-backdrop')!)
expect(toggle).toHaveAttribute('aria-expanded', 'false')
fireEvent.click(toggle)
fireEvent.click(within(sidebar).getByRole('button', { name: '收起笔记列表' }))
expect(toggle).toHaveAttribute('aria-expanded', 'false')
fireEvent.click(toggle)
await openNote(second.title)
expect(toggle).toHaveAttribute('aria-expanded', 'false')
expect(screen.getByLabelText('笔记标题')).toHaveValue(second.title)
})
it('pins and deletes through the persistent sidebar while detail is open', async () => {
+43 -15
View File
@@ -134,7 +134,7 @@ type MagicNotesLayoutPreferences = {
function loadMagicNotesLayoutPreferences(): MagicNotesLayoutPreferences {
const defaults = {
notesPaneOpen: true,
notesPaneWidth: 280,
notesPaneWidth: 200,
recordIndexOpen: false,
indexPaneWidth: defaultIndexPaneWidth,
aiPanePinned: false,
@@ -152,7 +152,7 @@ function loadMagicNotesLayoutPreferences(): MagicNotesLayoutPreferences {
return {
notesPaneOpen: parsed.notesPaneOpen !== false,
notesPaneWidth: typeof parsed.notesPaneWidth === 'number' && Number.isFinite(parsed.notesPaneWidth)
? Math.min(420, Math.max(240, parsed.notesPaneWidth)) : defaults.notesPaneWidth,
? Math.min(420, Math.max(160, parsed.notesPaneWidth)) : defaults.notesPaneWidth,
todoPaneWidth: typeof parsed.todoPaneWidth === 'number' && Number.isFinite(parsed.todoPaneWidth)
? Math.max(240, parsed.todoPaneWidth) : defaults.todoPaneWidth,
// Older layouts stored always-open panes; the compact defaults ignore those flags.
@@ -521,6 +521,7 @@ export function MagicNotesWorkspace({
const [indexPaneWidth, setIndexPaneWidth] = useState(initialLayoutPreferences.indexPaneWidth)
const [todoPaneWidth, setTodoPaneWidth] = useState(initialLayoutPreferences.todoPaneWidth)
const [notesPaneOpen, setNotesPaneOpen] = useState(initialLayoutPreferences.notesPaneOpen)
const [narrowNotesOpen, setNarrowNotesOpen] = useState(false)
const [notesPaneWidth, setNotesPaneWidth] = useState(initialLayoutPreferences.notesPaneWidth)
const [workspaceWidth, setWorkspaceWidth] = useState(window.innerWidth)
const [todoLayoutWidth, setTodoLayoutWidth] = useState(window.innerWidth)
@@ -696,19 +697,23 @@ export function MagicNotesWorkspace({
useEffect(() => {
const workspace = workspaceRef.current
if (!workspace) return
const update = (): void => setWorkspaceWidth(workspace.getBoundingClientRect().width || window.innerWidth)
const update = (): void => {
const width = workspace.getBoundingClientRect().width || window.innerWidth
setWorkspaceWidth(width)
if (width >= 600) setNarrowNotesOpen(false)
}
update()
const observer = new ResizeObserver(update)
observer.observe(workspace)
return () => observer.disconnect()
}, [loadStatus])
useLayoutEffect(() => {
useEffect(() => {
const stream = streamRef.current
if (!stream || !detail?.id) return
const position = noteScrollPositions.current.get(detail.id) ?? 0
if (requestedEntryIdRef.current) return
// Read-only rich text mounts in an effect; restore after it has laid out.
// Run after the read-only rich text effects have populated their content.
const frame = requestAnimationFrame(() => { stream.scrollTop = position })
return () => cancelAnimationFrame(frame)
}, [detail?.id, detailView])
@@ -1168,6 +1173,7 @@ export function MagicNotesWorkspace({
return
}
if (target.kind === 'overview') {
setNarrowNotesOpen(false)
discardComposerDraft()
discardEditingDraft()
detailRequestRef.current += 1
@@ -1183,6 +1189,7 @@ export function MagicNotesWorkspace({
return
}
if (target.kind === 'library-view') {
setNarrowNotesOpen(false)
if (target.value === 'todos') {
discardComposerDraft()
discardEditingDraft()
@@ -1218,6 +1225,7 @@ export function MagicNotesWorkspace({
setOverviewReturnNoteId(target.noteId)
}
setDetailView('notes')
setNarrowNotesOpen(false)
focusSwitchTarget(target)
void loadDetail(target.noteId, target.entryId)
},
@@ -1754,14 +1762,22 @@ export function MagicNotesWorkspace({
// Detail tiers: >800 three columns; 601-800 editor + AI with a record drawer; <=600 AI stacks below.
const isNarrowLayout = magicNotesLayoutWidth <= 800
const isStackedLayout = magicNotesLayoutWidth <= 600
const isNarrowWorkspace = workspaceWidth <= 900
const isNarrowWorkspace = workspaceWidth < 600
const paneResizeDisabled = (pane: 'ai' | 'index' | 'todo' | 'notes'): boolean =>
pane === 'notes' ? isNarrowWorkspace : pane === 'todo' ? todoLayoutWidth <= 700 : pane === 'ai' ? isStackedLayout : isNarrowLayout
const notesPaneWidthLimits = { minimum: 240, maximum: Math.min(420, Math.max(240, workspaceWidth - minimumMagicNotesEditorWidth - magicNotesResizeHandleWidth)) }
const notesPaneWidthLimits = { minimum: 160, maximum: Math.min(420, Math.max(160, workspaceWidth - minimumMagicNotesEditorWidth - magicNotesResizeHandleWidth)) }
const displayedNotesWidth = clampMagicNotesPaneWidth(notesPaneWidth, notesPaneWidthLimits)
// Overview shows every note full width; the split list appears only beside an open note.
const notesExpanded = isNarrowWorkspace ? !detailView : notesPaneOpen || !detailView
const notesDrawerOpen = libraryView === 'notes' && Boolean(detailView) && isNarrowWorkspace && narrowNotesOpen
const notesExpanded = isNarrowWorkspace ? !detailView || notesDrawerOpen : notesPaneOpen || !detailView
const notesSplit = libraryView === 'notes' && Boolean(detailView) && !isNarrowWorkspace && notesPaneOpen
const closeNotesDrawer = (): void => {
setNarrowNotesOpen(false)
document.getElementById('magic-notes-list-toggle')?.focus({ preventScroll: true })
}
useEffect(() => {
if (notesDrawerOpen) document.querySelector<HTMLInputElement>('#magic-library-panel-notes input[type="search"]')?.focus({ preventScroll: true })
}, [notesDrawerOpen])
const visibleNoteIdsKey = visibleNotes.map((note) => note.id).join('\n')
const visibleNoteIds = useMemo(
() => (visibleNoteIdsKey ? visibleNoteIdsKey.split('\n') : []),
@@ -2216,12 +2232,16 @@ export function MagicNotesWorkspace({
className="magic-note-list-item"
aria-current={selectedNoteId === note.id ? 'true' : undefined}
type="button"
onClick={() =>
selectedNoteId !== note.id && requestDraftSwitch({
onClick={() => {
if (selectedNoteId === note.id) {
if (notesDrawerOpen) closeNotesDrawer()
return
}
void requestDraftSwitch({
kind: 'note',
noteId: note.id
})
}
}}
>
<span className="magic-note-list-item__title">
{note.pinned && (
@@ -2359,9 +2379,10 @@ export function MagicNotesWorkspace({
<div
ref={workspaceRef}
aria-busy={Boolean(busy)}
className={`magic-notes-workspace${notesSplit ? ' magic-notes-workspace--notes' : ''}${libraryView === 'notes' && !detailView ? ' magic-notes-workspace--overview' : ''}${notesExpanded ? '' : ' magic-notes-workspace--list-hidden'}${isNarrowWorkspace ? ' magic-notes-workspace--narrow' : ''}${resizingPane === 'notes' ? ' magic-notes-layout--resizing' : ''}`}
className={`magic-notes-workspace${notesSplit || notesDrawerOpen ? ' magic-notes-workspace--notes' : ''}${notesDrawerOpen ? ' magic-notes-workspace--drawer' : ''}${libraryView === 'notes' && !detailView ? ' magic-notes-workspace--overview' : ''}${notesExpanded ? '' : ' magic-notes-workspace--list-hidden'}${isNarrowWorkspace ? ' magic-notes-workspace--narrow' : ''}${resizingPane === 'notes' ? ' magic-notes-layout--resizing' : ''}`}
style={{ '--magic-notes-notes-width': `${displayedNotesWidth}px` } as React.CSSProperties}
>
{notesDrawerOpen && <div className="magic-notes-list-backdrop" aria-hidden="true" onClick={closeNotesDrawer} />}
<section
aria-label={t(
libraryView === 'notes'
@@ -2370,6 +2391,12 @@ export function MagicNotesWorkspace({
)}
className={`magic-notes-overview${libraryView === 'todos' ? ' magic-notes-overview--todos' : ''}`}
hidden={libraryView === 'notes' ? !notesExpanded : Boolean(detailView)}
onKeyDown={(event) => {
if (notesDrawerOpen && event.key === 'Escape') {
event.stopPropagation()
closeNotesDrawer()
}
}}
>
{libraryView === 'notes' ? (
<div
@@ -2379,6 +2406,7 @@ export function MagicNotesWorkspace({
<div className="magic-notes-pane-heading">
<strong>{t('notes.heading')}</strong>
<span>{notes.length}</span>
{notesDrawerOpen && <button type="button" className="icon-button" aria-label={t('actions.hideNotes')} onClick={closeNotesDrawer}><PanelLeftClose aria-hidden="true" size={16} /></button>}
<MagicNoteTagManagerButton
compact={Boolean(detailView)}
disabled={Boolean(busy)}
@@ -2744,11 +2772,11 @@ export function MagicNotesWorkspace({
}}
>
<div className="magic-note-detail-toolbar" role="toolbar" aria-label={t('notes.toolbarLabel')}>
{libraryView === 'notes' && !isNarrowWorkspace && (
<button type="button" className="icon-button" aria-controls="magic-library-panel-notes"
{libraryView === 'notes' && (
<button id="magic-notes-list-toggle" type="button" className="icon-button" aria-controls="magic-library-panel-notes"
aria-expanded={notesExpanded} aria-label={t(notesExpanded ? 'actions.hideNotes' : 'actions.showNotes')}
title={t(notesExpanded ? 'actions.hideNotes' : 'actions.showNotes')}
onClick={() => setNotesPaneOpen((current) => !current)}>
onClick={() => isNarrowWorkspace ? setNarrowNotesOpen((current) => !current) : setNotesPaneOpen((current) => !current)}>
{notesExpanded ? <PanelLeftClose aria-hidden="true" size={16} /> : <PanelLeftOpen aria-hidden="true" size={16} />}
</button>
)}
+33 -1
View File
@@ -1283,6 +1283,7 @@
}
.magic-notes-workspace {
position: relative;
display: grid;
flex: 1;
min-width: 0;
@@ -1291,7 +1292,7 @@
}
.magic-notes-workspace--notes {
grid-template-columns: var(--magic-notes-notes-width, 280px) 9px minmax(0, 1fr);
grid-template-columns: var(--magic-notes-notes-width, 200px) 9px minmax(0, 1fr);
}
.magic-notes-workspace--list-hidden,
@@ -1327,6 +1328,24 @@
gap: var(--space-2);
}
.magic-notes-list-backdrop {
position: absolute;
inset: 0;
z-index: 4;
background: var(--overlay-backdrop);
}
.magic-notes-workspace--drawer > .magic-notes-overview {
position: absolute;
inset: 0 auto 0 0;
z-index: 5;
width: min(var(--magic-notes-notes-width, 200px), calc(100% - 32px));
padding: var(--space-2);
background: var(--surface-raised);
border-right: 1px solid var(--border-default);
box-shadow: var(--shadow-dialog);
}
.magic-notes-workspace--notes .magic-notes-pane-heading {
min-height: var(--magic-notes-heading-height);
}
@@ -15302,6 +15321,14 @@ textarea.field-control {
overflow-wrap: anywhere;
}
/* Windowed document table (P5): the table keeps the reader's row in place by
correcting the page's scrollTop; native scroll anchoring on the page could
pick a spacer and fight the correction. */
.workspace-panel-scroll:has(.knowledge-documents__table-scroll--windowed),
.knowledge-documents__table-scroll--windowed {
overflow-anchor: none;
}
/* Windowed document table (P5): spacer rows stand in for unmounted rows. */
.knowledge-documents__table-scroll tbody tr.knowledge-documents__spacer {
border: 0;
@@ -19431,6 +19458,11 @@ textarea.field-control {
min-height: 0;
overflow: auto;
}
/* A windowed tree keeps the reader's row in place itself; native scroll
anchoring could pick a spacer and fight the correction. */
.workspace-files__files-view--windowed {
overflow-anchor: none;
}
.workspace-git__changes {
flex: 1;
min-height: 0;
+28 -8
View File
@@ -94,7 +94,7 @@ it('resizes record columns with native Electron input and preserves desktop widt
const toolbarBox = await rect('.magic-note-detail-toolbar'), streamBox = await rect('.magic-notes-stream-pane');
const streamClient = await js('document.querySelector(".magic-notes-stream-pane").clientWidth');
assert(Math.abs(toolbarBox.x - streamBox.x) <= 1 && Math.abs(toolbarBox.width - streamClient) <= 1, 'Toolbar spans the stream: ' + JSON.stringify({ toolbarBox, streamBox, streamClient }));
assert.equal((await rect('.magic-notes-overview')).width, 280);
assert.equal((await rect('.magic-notes-overview')).width, 200);
assert(await js('document.querySelector(".magic-notes-overview").checkVisibility()'));
await js('document.querySelector(".magic-notes-card-grid").scrollTop = 300; document.querySelector(".magic-notes-stream-pane").scrollTop = 420');
await wait('document.querySelector(".magic-notes-stream-pane").scrollTop === 420');
@@ -108,7 +108,7 @@ it('resizes record columns with native Electron input and preserves desktop widt
assert.equal(await js('document.querySelector("#magic-note-select-layout-note").getAttribute("aria-current")'), 'true');
await drag('.magic-notes-notes-resize-handle', 40);
const notesWidth = (await rect('.magic-notes-overview')).width;
assert(notesWidth >= 320 && notesWidth <= 325);
assert(notesWidth >= 240 && notesWidth <= 245);
await key('.magic-notes-notes-resize-handle', 'Left');
assert.equal((await rect('.magic-notes-overview')).width, notesWidth - 16);
await click('button[aria-controls="magic-library-panel-notes"]');
@@ -117,7 +117,7 @@ it('resizes record columns with native Electron input and preserves desktop widt
assert.equal((await rect('.magic-notes-overview')).width, notesWidth - 16);
assert.equal(await js('document.querySelector(".magic-notes-card-grid").scrollTop'), 300);
await key('.magic-notes-notes-resize-handle', 'Home');
assert.equal((await rect('.magic-notes-overview')).width, 240);
assert.equal((await rect('.magic-notes-overview')).width, 160);
await js('document.querySelector(".magic-notes-stream-pane").scrollTop = 0');
const editor = '.magic-note-composer .magic-note-editor__content .ql-editor';
const saveButton = '.magic-note-composer > footer > .primary-button';
@@ -184,10 +184,10 @@ it('resizes record columns with native Electron input and preserves desktop widt
const withoutAi = (await rect(index)).width; assert(withoutAi < 250 && withoutAi > 230);
await click('button[aria-controls="magic-notes-ai-pane"]'); assert.equal((await rect(index)).width, withoutAi);
await win.loadURL(${JSON.stringify(server.resolvedUrls!.local[0] + 'layout.html')}); await open(); assert.equal((await rect(index)).width, withoutAi);
assert.equal((await rect('.magic-notes-overview')).width, 240);
assert.equal((await rect('.magic-notes-overview')).width, 160);
await js('document.querySelector(".magic-notes-card-grid").scrollTop = 300');
win.setContentSize(1000, 720); await settle(); await settle();
assert.equal((await rect('.magic-notes-overview')).width, 240);
win.setContentSize(960, 720); await settle(); await settle();
assert.equal((await rect('.magic-notes-overview')).width, 160);
assert.equal((await rect(index)).width, 0, 'Record drawer follows detail width, not the outer page');
const assertSideBySide = async () => {
const s = await rect(stream), ai = await rect('.magic-notes-ai-pane'), list = await rect('.magic-notes-overview');
@@ -198,12 +198,14 @@ it('resizes record columns with native Electron input and preserves desktop widt
await assertSideBySide();
win.setContentSize(930, 720); await settle(); await settle();
await assertSideBySide();
win.setContentSize(1000, 720); await settle(); await settle();
win.setContentSize(960, 720); await settle(); await settle();
assert(await js('document.documentElement.scrollWidth <= innerWidth'));
win.setContentSize(820, 720); await settle(); await settle();
assert((await rect(stream)).width >= 299);
const constrained = { index: (await rect(index)).width, editor: (await rect(stream)).width, ai: (await rect('.magic-notes-ai-pane')).width };
win.setContentSize(720, 640); await settle(); await settle();
assert.equal((await rect('.magic-notes-overview')).width, 160);
await click('#magic-notes-list-toggle');
assert.equal((await rect('.magic-notes-overview')).width, 0);
await click('#magic-notes-back');
assert(await js('document.querySelector(".magic-notes-overview").checkVisibility()'));
@@ -224,10 +226,28 @@ it('resizes record columns with native Electron input and preserves desktop widt
assert(Math.abs(narrowFooter.x + narrowFooter.width - footerPaddingRight - narrowSave.x - narrowSave.width) <= 1, 'Narrow Save button stays right-aligned within footer padding');
await screenshot('magic-notes-narrow');
win.setContentSize(560, 640); await settle(); await settle();
assert(await js('document.querySelector("#magic-notes-list-toggle").checkVisibility()'));
const detailBeforeDrawer = await rect('.magic-notes-detail-container');
await click('#magic-notes-list-toggle');
assert(await js('document.querySelector(".magic-notes-workspace--drawer") !== null'));
assert.equal((await rect('.magic-notes-overview')).width, 160);
assert.deepEqual(await rect('.magic-notes-detail-container'), detailBeforeDrawer);
assert(await js('document.querySelector(".magic-notes-overview").scrollWidth <= document.querySelector(".magic-notes-overview").clientWidth'));
await key('#magic-library-panel-notes input', 'Escape');
assert.equal((await rect('.magic-notes-overview')).width, 0);
assert(await js('document.activeElement.id === "magic-notes-list-toggle"'));
await click('#magic-notes-list-toggle');
await click('#magic-note-select-other-note-3');
await wait('document.querySelector(".magic-note-detail-title").value === "Other note 3"');
assert.equal((await rect('.magic-notes-overview')).width, 0);
await click('#magic-notes-list-toggle');
await click('#magic-note-select-layout-note');
await wait('document.querySelector(".magic-note-detail-title").value === "Layout note"');
assert((await rect('.magic-notes-ai-pane')).y >= (await rect(stream)).y + (await rect(stream)).height, 'AI pane stacks below the stream on very narrow widths');
assert.equal(await js('getComputedStyle(document.querySelector(".magic-notes-ai-resize-handle")).display'), 'none');
win.setContentSize(1280, 800); await settle(); await settle();
assert.equal((await rect('.magic-notes-overview')).width, 240);
await click('#magic-notes-list-toggle');
assert.equal((await rect('.magic-notes-overview')).width, 160);
assert.equal((await rect(index)).width, withoutAi);
await key('.magic-note-record', 'Tab');
await js('document.querySelector(".magic-note-record").focus()');