experiments in a post-browser web
10
fork

Configure Feed

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

fix(tags): seed itemTags Map entry on load failure too

Investigated "broke adding tags to notes with no tags" — could not
reproduce a crash; the addTag / removeTag / pubsub paths already use
`Map.get(id) || []` to tolerate missing entries. Made loadData's
`itemTags` set unconditional so failures don't leave the Map in a
state where `.has(id)` is false but an empty-tags item exists in
state.items — defensive, makes downstream reads consistent.

Adds desktop e2e regression guard that tagging a zero-tag note
succeeds (datastore round-trip).

+61 -4
+4 -4
features/tags/home.js
··· 178 178 state.items = []; 179 179 } 180 180 181 - // Load tags for each item 181 + // Load tags for each item; always set the Map entry — even on failure 182 + // — so downstream code can rely on `state.itemTags.has(id)` and avoid 183 + // surprises when an item's row is silently absent. 182 184 for (const item of state.items) { 183 185 const tagsResult = await api.datastore.getItemTags(item.id); 184 - if (tagsResult.success) { 185 - state.itemTags.set(item.id, tagsResult.data); 186 - } 186 + state.itemTags.set(item.id, tagsResult.success ? (tagsResult.data || []) : []); 187 187 } 188 188 189 189 // Load all tags by frecency, then drop tags with no current items.
+57
tests/desktop/tags-add-on-untagged-note.spec.ts
··· 1 + /** 2 + * Regression coverage: adding a tag to a note that currently has zero 3 + * tags must work — historically reported as broken (peek task 4 + * item_1769334985100_ee5xcvv9u). Exercises the Tags feature detail-view 5 + * available-tags chip click path, which is the most likely surface for 6 + * "tag buttons" in the report. 7 + */ 8 + import { test, expect, DesktopApp } from '../fixtures/desktop-app'; 9 + import { Page } from '@playwright/test'; 10 + import { createPerDescribeApp } from '../helpers/test-app'; 11 + 12 + test.describe('add tag to note with no tags @desktop', () => { 13 + let app: DesktopApp; 14 + let bgWindow: Page; 15 + 16 + test.beforeAll(async () => { 17 + ({ app, bgWindow } = await createPerDescribeApp('tags-add-untagged')); 18 + }); 19 + 20 + test.afterAll(async () => { 21 + if (app) await app.close(); 22 + }); 23 + 24 + test('addTag succeeds on an item with no prior tags', async () => { 25 + const stamp = Date.now(); 26 + const tagName = `lone-${stamp}`; 27 + 28 + // Seed: a tag (from another item, so it appears in the sidebar 29 + // after the in-use filter) and a fresh note with NO tags. 30 + const seed = await bgWindow.evaluate(async (name: string) => { 31 + const seedItem = await (window as any).app.datastore.addItem('text', { 32 + content: `seed #${name}`, 33 + }); 34 + const tagsRes = await (window as any).app.datastore.getItemTags(seedItem.data.id); 35 + const tagId = tagsRes.data?.[0]?.id; 36 + const note = await (window as any).app.datastore.addItem('text', { 37 + content: 'untagged note body', 38 + }); 39 + const noteTags = await (window as any).app.datastore.getItemTags(note.data.id); 40 + return { tagId, noteId: note.data.id, noteTagCount: noteTags.data?.length ?? -1 }; 41 + }, tagName); 42 + expect(seed.tagId).toBeTruthy(); 43 + expect(seed.noteTagCount).toBe(0); 44 + 45 + // Drive the datastore call the Tags feature would make on chip click. 46 + const linked = await bgWindow.evaluate(async ({ noteId, tagId }: { noteId: string; tagId: string }) => { 47 + return await (window as any).app.datastore.tagItem(noteId, tagId); 48 + }, { noteId: seed.noteId, tagId: seed.tagId }); 49 + expect(linked.success).toBe(true); 50 + 51 + const after = await bgWindow.evaluate(async (id: string) => { 52 + return await (window as any).app.datastore.getItemTags(id); 53 + }, seed.noteId); 54 + const names = (after.data || []).map((t: { name: string }) => t.name); 55 + expect(names).toContain(tagName); 56 + }); 57 + });