fix: group and label note page controls

Move the record index toggle beside the back button and show text labels for both index and AI comment controls. Preserve pane visibility, keyboard focus, and narrow-window behavior; update tests and documentation.

Validation: 112 workspace tests and Electron layout regression passed; typecheck and lint passed. Full suite hit unrelated test failures and timed out.
This commit is contained in:
mesalogo
2026-09-23 15:47:54 +08:00
parent fb4b9bdd46
commit 0265cfb762
3 changed files with 40 additions and 25 deletions
@@ -54,8 +54,10 @@ and 320px. It shares the AI pane's pointer-capture handlers, separator styling
and keyboard semantics (16px arrow steps, Home/End limits). Both widths are
stored in `goodbuddy.magic-notes-layout.v1`; missing index width defaults to
168px. Displayed widths clamp together to reserve at least 300px for the stream,
without overwriting saved widths when the window shrinks. The index toggle stays in the detail header immediately
before the title; collapsing hides the entire index and removes its grid column,
without overwriting saved widths when the window shrinks. The page header places
the index toggle immediately after the back button, followed by the AI toggle.
All three use icons and visible localized text; pane toggle labels reflect their
current show/hide action. Collapsing hides the entire index and removes its grid column,
border, resize separator and occupied width. AI has a separate
right pane with its existing resize separator and header toggle. Both panes scroll
independently and persist independent desktop visibility. At container widths of
+17 -8
View File
@@ -824,10 +824,10 @@ describe('MagicNotesWorkspace detail behavior', () => {
await openNote()
showAi()
const toggle = screen.getByRole('button', { name: '隐藏 AI 评论', expanded: true })
expect(toggle).toHaveClass('icon-button', 'icon-button--active')
expect(toggle).toHaveClass('secondary-button')
expect(toggle).toHaveAttribute('title', '隐藏 AI 评论')
expect(toggle).toHaveAttribute('aria-controls', 'magic-notes-ai-pane')
expect(toggle.textContent).toBe('')
expect(toggle).toHaveTextContent('隐藏 AI 评论')
const pane = screen.getByRole('complementary', { name: 'AI 评论' })
expect(pane).toHaveAttribute('id', toggle.getAttribute('aria-controls'))
expect(within(pane).queryByText('AI 评论', { exact: true })).not.toBeInTheDocument()
@@ -841,7 +841,7 @@ describe('MagicNotesWorkspace detail behavior', () => {
expect(toggle).toHaveFocus()
expect(screen.getByRole('button', { name: '显示 AI 评论', expanded: false })).toBe(toggle)
expect(toggle).toHaveAttribute('title', '显示 AI 评论')
expect(toggle).not.toHaveClass('icon-button--active')
expect(toggle).toHaveTextContent('显示 AI 评论')
expect(screen.queryByRole('complementary', { name: 'AI 评论' })).not.toBeInTheDocument()
back()
await openNote()
@@ -956,7 +956,7 @@ describe('MagicNotesWorkspace detail behavior', () => {
it('keeps entry editing contained and supports deletion confirmation', async () => {
const { container } = render(<MagicNotesWorkspace onNotify={onNotify} />)
await openNote()
expect(container.querySelector('.magic-note-detail-header button + input')).toBe(screen.getByLabelText('笔记标题'))
expect(container.querySelector('.magic-note-detail-header > :first-child')).toBe(screen.getByLabelText('笔记标题'))
expect(screen.getByRole('button', { name: '删除记录' })).toHaveClass('danger-button--quiet')
fireEvent.click(screen.getByRole('button', { name: '编辑' }))
expect(container.querySelector('.magic-note-entry__editor')).toBeVisible()
@@ -1187,8 +1187,14 @@ describe('MagicNotesWorkspace canvas integration', () => {
fireEvent.click(screen.getByRole('button', { name: '编辑' }))
const editor = screen.getByTestId('magic-note-editor')
const toggle = screen.getByRole('button', { name: '收起记录索引' })
expect(toggle.parentElement).toHaveClass('magic-note-detail-header')
expect(toggle.nextElementSibling).toBe(screen.getByLabelText('笔记标题'))
expect(toggle.parentElement).toHaveClass('page-header__actions')
const back = screen.getByRole('button', { name: '返回总览' })
expect(back).toHaveTextContent('返回总览')
expect(back.nextElementSibling).toBe(toggle)
expect(toggle).toHaveTextContent('收起记录索引')
const aiToggle = screen.getByRole('button', { name: '隐藏 AI 评论' })
expect(toggle.nextElementSibling).toBe(aiToggle)
expect(aiToggle).toHaveTextContent('隐藏 AI 评论')
expect(toggle).toHaveAttribute('aria-controls', 'magic-notes-index')
expect(toggle).toHaveAttribute('aria-expanded', 'true')
toggle.focus()
@@ -1196,23 +1202,26 @@ describe('MagicNotesWorkspace canvas integration', () => {
expect(toggle).toHaveFocus()
expect(toggle).toHaveAttribute('aria-expanded', 'false')
expect(toggle).toHaveAttribute('title', '展开记录索引')
expect(toggle).toHaveTextContent('展开记录索引')
expect(document.querySelector('.magic-notes-index-pane')).toHaveAttribute('hidden')
expect(document.querySelector('.magic-notes-layout')).toHaveClass('magic-notes-layout--index-hidden')
expect(screen.queryByRole('navigation', { name: '记录' })).not.toBeInTheDocument()
expect(screen.getByRole('complementary', { name: 'AI 评论' })).toBeVisible()
fireEvent.click(screen.getByRole('button', { name: '隐藏 AI 评论' }))
expect(aiToggle).toHaveTextContent('显示 AI 评论')
fireEvent.click(screen.getByRole('button', { name: '展开记录索引' }))
expect(screen.getByRole('navigation', { name: '记录' })).toBeVisible()
expect(screen.queryByRole('complementary', { name: 'AI 评论' })).not.toBeInTheDocument()
expect(screen.getByTestId('magic-note-editor')).toBe(editor)
})
it('opens and closes the narrow index from the detail header and returns Escape focus', async () => {
it('opens and closes the narrow index beside the back button and returns Escape focus', async () => {
vi.spyOn(window, 'innerWidth', 'get').mockReturnValue(700)
render(<MagicNotesWorkspace onNotify={onNotify} />)
await openNote()
const toggle = screen.getByRole('button', { name: '展开记录索引' })
expect(toggle.nextElementSibling).toBe(screen.getByLabelText('笔记标题'))
expect(screen.getByRole('button', { name: '返回总览' }).nextElementSibling).toBe(toggle)
expect(toggle).toHaveTextContent('展开记录索引')
expect(document.querySelector('.magic-notes-index-pane')).toHaveAttribute('hidden')
fireEvent.click(toggle)
expect(toggle).toHaveAttribute('aria-expanded', 'true')
+19 -15
View File
@@ -1946,11 +1946,28 @@ export function MagicNotesWorkspace({
</button>
)}
{detailView && (
<button
id="magic-notes-index-toggle"
type="button"
className="secondary-button"
aria-controls="magic-notes-index"
aria-expanded={indexExpanded}
aria-label={t(indexExpanded ? 'records.hide' : 'records.show')}
title={t(indexExpanded ? 'records.hide' : 'records.show')}
onClick={() => isNarrowLayout
? setNarrowIndexOpen((current) => !current)
: setIndexPaneOpen((current) => !current)}
>
{indexExpanded ? <PanelLeftClose aria-hidden="true" size={15} /> : <PanelLeftOpen aria-hidden="true" size={15} />}
{t(indexExpanded ? 'records.hide' : 'records.show')}
</button>
)}
{detailView && (
<button
aria-controls="magic-notes-ai-pane"
aria-expanded={aiPaneOpen}
aria-label={t(aiPaneOpen ? 'actions.hideAiComments' : 'actions.showAiComments')}
className={`icon-button${aiPaneOpen ? ' icon-button--active' : ''}`}
className="secondary-button"
onClick={() => setAiPaneOpen((current) => !current)}
title={t(aiPaneOpen ? 'actions.hideAiComments' : 'actions.showAiComments')}
type="button"
@@ -1960,6 +1977,7 @@ export function MagicNotesWorkspace({
) : (
<PanelRightOpen aria-hidden="true" size={15} />
)}
{t(aiPaneOpen ? 'actions.hideAiComments' : 'actions.showAiComments')}
</button>
)}
{!detailView && (
@@ -2496,20 +2514,6 @@ export function MagicNotesWorkspace({
</div>
)}
<header className="magic-note-detail-header">
<button
id="magic-notes-index-toggle"
type="button"
className="icon-button"
aria-controls="magic-notes-index"
aria-expanded={indexExpanded}
aria-label={t(indexExpanded ? 'records.hide' : 'records.show')}
title={t(indexExpanded ? 'records.hide' : 'records.show')}
onClick={() => isNarrowLayout
? setNarrowIndexOpen((current) => !current)
: setIndexPaneOpen((current) => !current)}
>
{indexExpanded ? <PanelLeftClose aria-hidden="true" size={16} /> : <PanelLeftOpen aria-hidden="true" size={16} />}
</button>
<input
aria-describedby={
validation?.target === 'note-title'