feat(v0.6): add Home (⌂) button to reach Sessions/Projects from a session

Once a project/session tab was open, the home chooser (Sessions/Projects
segmented control) was hidden and only reachable by closing every tab — no
way back to the Projects view to open another project.

Add a ⌂ Home button to the tab bar that overlays the chooser on top of the
open tabs (homeForced flag; updateHomeView shows home when no tab OR forced,
hiding the terminal panes). Clicking ⌂ again, clicking a tab (activate resets
homeForced), or opening another session/project dismisses the overlay.
Closing the last tab resets the flag. The button highlights while active.

Tests: +3 (tabs.test.ts). Full suite 415 green; both typechecks clean;
verified in-browser (open tab → ⌂ → Projects panel reachable → ⌂ → terminal).
This commit is contained in:
Yaojia Wang
2026-06-30 11:39:38 +02:00
parent 7b4adf5072
commit 985ff8a178
4 changed files with 117 additions and 2 deletions

View File

@@ -59,6 +59,11 @@ export class TabApp {
private readonly projects: ProjectsPanel
private homeView: HomeView = 'sessions'
private readonly segControl: HTMLElement
// When true, the home chooser is overlaid on top of the open tabs (so you can
// jump back to Sessions/Projects without closing anything). Reset whenever a
// tab is activated. Irrelevant while no tab is open (home shows regardless).
private homeForced = false
private homeBtn: HTMLButtonElement | null = null
constructor(paneHost: HTMLElement, tabBar: HTMLElement) {
this.paneHost = paneHost
@@ -104,7 +109,9 @@ export class TabApp {
* When no tab is open: show the seg control and the selected sub-panel.
*/
private updateHomeView(): void {
const empty = this.tabs.length === 0
// Home shows when there is no tab OR the user explicitly forced it (the ⌂
// button), letting you reach Sessions/Projects without closing a session.
const showHome = this.tabs.length === 0 || this.homeForced
// Sync active-class on segmented control buttons.
const btns = this.segControl.querySelectorAll<HTMLButtonElement>('.home-seg-btn')
@@ -112,8 +119,12 @@ export class TabApp {
const view: HomeView = i === 0 ? 'sessions' : 'projects'
btn.classList.toggle('active', view === this.homeView)
})
// The ⌂ button is "pressed" only while overlaying home on top of open tabs.
this.homeBtn?.classList.toggle('active', this.homeForced && this.tabs.length > 0)
if (empty) {
if (showHome) {
// Overlay home: hide every terminal pane so the chooser is on top.
for (const t of this.tabs) t.session.hide()
this.segControl.style.display = 'flex'
this.launcher.setVisible(this.homeView === 'sessions')
this.projects.setVisible(this.homeView === 'projects')
@@ -124,6 +135,19 @@ export class TabApp {
}
}
/** Toggle the home chooser overlay over the open tabs (the ⌂ button). No-op
* when no tab is open (home is already showing). */
private toggleHome(): void {
if (this.tabs.length === 0) return
this.homeForced = !this.homeForced
if (this.homeForced) {
this.updateHomeView()
} else {
// Return to the active terminal (activate re-shows its pane + hides home).
this.activate(this.activeIndex)
}
}
private saveHomeView(): void {
try {
localStorage.setItem(HOME_VIEW_KEY, this.homeView)
@@ -310,11 +334,13 @@ export class TabApp {
activate(i: number): void {
if (i < 0 || i >= this.tabs.length) return
this.maybeAskNotify() // first switch is a user gesture — request notif permission
this.homeForced = false // showing a terminal dismisses any home overlay
this.activeIndex = i
const entry = this.tabs[i]
if (entry) entry.hasActivity = false // viewing clears the unread dot
this.tabs.forEach((t, idx) => (idx === i ? t.session.show() : t.session.hide()))
this.tabs.forEach((t) => this.refreshTab(t)) // in-place class/text update, no rebuild
this.updateHomeView() // hide the seg control + home panels now a tab is active
this.updateApprovalBar()
this.persist()
}
@@ -327,6 +353,7 @@ export class TabApp {
if (this.tabs.length === 0) {
// v0.5: closing the last tab returns to the home screen — no auto-blank tab.
this.activeIndex = -1
this.homeForced = false // back to the natural "no tabs → home" state
this.persist()
this.rebuild() // updateHomeView() (in rebuild) shows the chooser
this.updateApprovalBar()
@@ -459,6 +486,17 @@ export class TabApp {
private rebuild(): void {
this.tabBar.replaceChildren()
// Home (⌂): overlay the Sessions/Projects chooser over the open tabs so you
// can start another session/project without closing the current one.
const home = document.createElement('button')
home.className = 'tab-home'
home.textContent = '⌂'
home.title = 'Home — Sessions & Projects'
home.setAttribute('aria-label', 'Home — Sessions & Projects')
home.addEventListener('click', () => this.toggleHome())
this.tabBar.appendChild(home)
this.homeBtn = home
this.tabs.forEach((entry, idx) => {
const tabEl = document.createElement('div')
entry.el = tabEl