A local-first note taking app
0

Configure Feed

Select the types of activity you want to include in your feed.

Tabs per tile (phase 3): focus-or-append open flow

Opening a document no longer replaces the active tab. New pure helper
tabsModel.planOpen decides: focus an existing tab showing the same
document (no reopen), else reuse the active empty start tab, else append
a new tab. makeTabId is only called when a tab is actually created.

useTilingDocumentSlots routes opens through planOpen and skips
startDocumentOpen when the document was already open (focus only).

Also make the active-tab highlight switch instantly: tab chips opt out
of the global 320ms color transition (transition-none) so selecting a
tab updates the highlight immediately.

Fix tab switches sometimes showing the previous file: key DocumentPane
by document id in DocumentSlotView so switching to a different document
remounts a fresh editor. The CodeMirror view binds content on mount
only, and the in-place content swap was skippable by the isWriting
echo-suppression lock, so a reused editor could keep showing the old
file even with the new tab marked active.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Ethan Graf (Jun 24, 2026, 11:18 PM EDT) eb9ac19f ae08d701

+98 -10
+5
src/editors/DocumentSlotView.tsx
··· 42 42 ); 43 43 } 44 44 return ( 45 + // Key by document id so switching tabs to a different document remounts a 46 + // fresh editor instead of reusing one bound to the previous document. The 47 + // shared editor view binds its content on mount only, so without this a 48 + // tab switch can leave the previous file's content showing. 45 49 <DocumentPane 50 + key={state.handle.id} 46 51 handle={state.handle} 47 52 placeholder={placeholder} 48 53 className={className}
+1 -1
src/workspaces/tiling/TileTabStrip.tsx
··· 60 60 tabIndex={isActive ? 0 : -1} 61 61 onMouseDown={(e) => handleAuxClose(e, tab.tabId)} 62 62 onClick={() => onSelect(tab.tabId)} 63 - className={`group flex shrink-0 cursor-pointer items-center gap-1 rounded px-2 py-1 text-xs ${ 63 + className={`group flex shrink-0 cursor-pointer items-center gap-1 rounded px-2 py-1 text-xs transition-none ${ 64 64 isActive 65 65 ? 'bg-accent/15 text-foreground' 66 66 : 'text-foreground/60 hover:bg-border/30'
+40
src/workspaces/tiling/tabsModel.test.ts
··· 11 11 getActiveSlot, 12 12 getActiveTabId, 13 13 getTabs, 14 + planOpen, 14 15 setActiveTab, 15 16 setTabSlot, 16 17 type TileTabsMap, ··· 143 144 'tab-blank', 144 145 ); 145 146 expect(findTabForRequest(map, 'tile-1', request('a'))).toBe('tab-a'); 147 + }); 148 + }); 149 + 150 + describe('planOpen', () => { 151 + const makeId = () => 'tab-new'; 152 + 153 + it('focuses an existing tab without creating a new one', () => { 154 + const map = tileWithDocs('tile-1', ['a', 'b'], 'a'); 155 + const plan = planOpen(map, 'tile-1', request('b'), makeId); 156 + expect(plan.alreadyOpen).toBe(true); 157 + expect(plan.tabId).toBe('tab-b'); 158 + expect(getActiveTabId(plan.map, 'tile-1')).toBe('tab-b'); 159 + expect(getTabs(plan.map, 'tile-1')).toHaveLength(2); 160 + }); 161 + 162 + it('reuses the active empty start tab', () => { 163 + const map = ensureTile({}, 'tile-1', 'tab-1'); 164 + const plan = planOpen(map, 'tile-1', request('a'), makeId); 165 + expect(plan.alreadyOpen).toBe(false); 166 + expect(plan.tabId).toBe('tab-1'); 167 + expect(getTabs(plan.map, 'tile-1')).toHaveLength(1); 168 + }); 169 + 170 + it('appends a new tab when the active tab holds a document', () => { 171 + const map = tileWithDocs('tile-1', ['a'], 'a'); 172 + const plan = planOpen(map, 'tile-1', request('b'), makeId); 173 + expect(plan.alreadyOpen).toBe(false); 174 + expect(plan.tabId).toBe('tab-new'); 175 + expect(getTabs(plan.map, 'tile-1').map((t) => t.tabId)).toEqual([ 176 + 'tab-a', 177 + 'tab-new', 178 + ]); 179 + expect(getActiveTabId(plan.map, 'tile-1')).toBe('tab-new'); 180 + }); 181 + 182 + it('creates the tile when it does not exist yet', () => { 183 + const plan = planOpen({}, 'tile-1', request('a'), makeId); 184 + expect(plan.tabId).toBe('tab-new'); 185 + expect(getActiveTabId(plan.map, 'tile-1')).toBe('tab-new'); 146 186 }); 147 187 }); 148 188
+41
src/workspaces/tiling/tabsModel.ts
··· 155 155 return tile.tabs.find((t) => t.slot.kind === 'empty')?.tabId ?? null; 156 156 } 157 157 158 + export type OpenPlan = { 159 + map: TileTabsMap; 160 + /** The tab the document should open into (or that was focused). */ 161 + tabId: string; 162 + /** True when the document was already open and we only changed focus. */ 163 + alreadyOpen: boolean; 164 + }; 165 + 166 + /** 167 + * Decide where an open lands in a tile: 168 + * 1. Focus an existing tab already showing the same document (no reopen). 169 + * 2. Otherwise reuse the active empty "start" tab if there is one. 170 + * 3. Otherwise append a new tab (creating the tile if absent). 171 + * 172 + * `makeTabId` is only invoked when a new tab is actually created, so callers' 173 + * id counters do not advance on focus/reuse. 174 + */ 175 + export function planOpen( 176 + map: TileTabsMap, 177 + tileId: string, 178 + request: OpenDocRequest, 179 + makeTabId: () => string, 180 + ): OpenPlan { 181 + const existing = findTabForRequest(map, tileId, request); 182 + if (existing) { 183 + return { 184 + map: setActiveTab(map, tileId, existing), 185 + tabId: existing, 186 + alreadyOpen: true, 187 + }; 188 + } 189 + 190 + const activeTabId = getActiveTabId(map, tileId); 191 + if (activeTabId !== null && getActiveSlot(map, tileId).kind === 'empty') { 192 + return { map, tabId: activeTabId, alreadyOpen: false }; 193 + } 194 + 195 + const tabId = makeTabId(); 196 + return { map: addTab(map, tileId, tabId), tabId, alreadyOpen: false }; 197 + } 198 + 158 199 export type CloseTabResult = { 159 200 map: TileTabsMap; 160 201 /** Tab ids whose document handles the caller must release. */
+11 -9
src/workspaces/tiling/useTilingDocumentSlots.ts
··· 77 77 const request = openRequest; 78 78 const tileId = activeTileId; 79 79 80 - // Open into the active tile's active tab. (Focus-or-append routing arrives 81 - // in a later phase, once the tab strip exists.) 82 - let map = tileTabsRef.current; 83 - if (!map[tileId]) { 84 - map = tabsModel.ensureTile(map, tileId, makeTabId()); 85 - setTileTabs(map); 86 - } 87 - const tabId = tabsModel.getActiveTabId(map, tileId); 88 - if (!tabId) return; 80 + // Focus an existing tab for this document, reuse the empty start tab, or 81 + // append a new tab — all decided by tabsModel.planOpen. 82 + const plan = tabsModel.planOpen( 83 + tileTabsRef.current, 84 + tileId, 85 + request, 86 + makeTabId, 87 + ); 88 + setTileTabs(plan.map); 89 + if (plan.alreadyOpen) return; 90 + const tabId = plan.tabId; 89 91 90 92 const setSlot = (slot: DocumentSlotState) => 91 93 setTileTabs((prev) => tabsModel.setTabSlot(prev, tileId, tabId, slot));