fix(app): keep the last document tab closable

This commit is contained in:
Victor Wads 2026-08-17 20:20:23 -03:00
parent 52a8e5da62
commit a9e998f9e4
No known key found for this signature in database
5 changed files with 29 additions and 9 deletions

View file

@ -46,6 +46,7 @@
### Changed
- Open new tabs on Recent Files so users can choose a document before creating a blank canvas.
- Pan horizontally with Shift+wheel while preserving native horizontal trackpad movement.
- Move MCP connections into their own Settings destination instead of presenting them as part of model configuration.

View file

@ -45,6 +45,7 @@ export const allTabs = computed(() =>
tabsRef.value.map((t) => ({
id: t.id,
name: t.store.state.documentName,
isHome: t.showHome,
isActive: t.id === activeTabId.value
}))
)
@ -81,8 +82,9 @@ export function createTab(store?: EditorStore, initialGraph?: SceneGraph, showHo
export function leaveHome(tabId: string): void {
const tabIndex = tabsRef.value.findIndex((candidate) => candidate.id === tabId)
if (tabIndex === -1) return
const tab = tabsRef.value[tabIndex]
if (!tab?.showHome) return
if (!tab.showHome) return
tabsRef.value = tabsRef.value.with(tabIndex, { ...tab, showHome: false })
}

View file

@ -19,6 +19,10 @@ const modelValue = computed({
set: (id: string) => switchTab(id)
})
function openRecentFilesTab() {
createTab(undefined, undefined, true)
}
function onMiddleClick(e: MouseEvent, tabId: string) {
if (e.button === 1) {
e.preventDefault()
@ -34,7 +38,7 @@ function onClose(e: MouseEvent, tabId: string) {
<template>
<TabsRoot
v-if="tabs.length > 1"
v-if="tabs.length > 0"
v-model="modelValue"
activation-mode="automatic"
:class="baseStyles.root()"
@ -49,14 +53,15 @@ function onClose(e: MouseEvent, tabId: string) {
:data-active="tab.isActive || undefined"
@mousedown="onMiddleClick($event, tab.id)"
>
<icon-lucide-file :class="baseStyles.icon()" />
<span :class="baseStyles.label()">{{ tab.name }}</span>
<Tip :label="dialogs.closeTab({ name: tab.name })">
<icon-lucide-house v-if="tab.isHome" :class="baseStyles.icon()" />
<icon-lucide-file v-else :class="baseStyles.icon()" />
<span :class="baseStyles.label()">{{ tab.isHome ? dialogs.recentFiles : tab.name }}</span>
<Tip :label="dialogs.closeTab({ name: tab.isHome ? dialogs.recentFiles : tab.name })">
<button
data-test-id="tabbar-close"
:class="tabBarStyles({ active: tab.isActive }).close()"
:data-active="tab.isActive || undefined"
:aria-label="dialogs.closeTab({ name: tab.name })"
:aria-label="dialogs.closeTab({ name: tab.isHome ? dialogs.recentFiles : tab.name })"
tabindex="-1"
@click="onClose($event, tab.id)"
>
@ -70,7 +75,7 @@ function onClose(e: MouseEvent, tabId: string) {
data-test-id="tabbar-new"
:class="baseStyles.newAction()"
:aria-label="dialogs.newTab"
@click="createTab()"
@click="openRecentFilesTab"
>
<icon-lucide-plus :class="baseStyles.newIcon()" />
</button>

View file

@ -127,7 +127,7 @@ onUnmounted(() => {
<FontStatusBanner />
<RenameSelectionDialog />
<RecentFilesHome v-if="showingHome" @new-document="activeTab && leaveHome(activeTab.id)" />
<TabBar v-if="!showingHome" />
<TabBar />
<!-- Desktop layout -->
<SplitterGroup

View file

@ -9,5 +9,17 @@ test('opens to the recent-files home and starts a new document', async ({ page }
await page.getByTestId('home-new-document').click()
await expect(page.getByTestId('recent-files-home')).toBeHidden()
await expect(page.getByRole('group', { name: 'Layers' })).toBeVisible()
const tab = page.getByTestId('tabbar-tab')
await expect(tab).toBeVisible()
await page.getByTestId('tabbar-new').click()
await expect(page.getByTestId('recent-files-home')).toBeVisible()
await expect(page.getByTestId('tabbar-tab')).toHaveCount(2)
await expect(page.getByTestId('tabbar-tab').last()).toContainText('Recent files')
await page.getByTestId('tabbar-tab').last().hover()
await page.getByTestId('tabbar-tab').last().getByTestId('tabbar-close').click()
await expect(page.getByTestId('recent-files-home')).toBeHidden()
})