fix: restrict inbox to captured artifacts

This commit is contained in:
2026-06-05 01:35:27 +08:00
parent 58a74acbf6
commit 2e86229350
9 changed files with 34 additions and 30 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -16,7 +16,7 @@
background: #13131f;
}
</style>
<script type="module" crossorigin src="/assets/main-4y6wyoK9.js"></script>
<script type="module" crossorigin src="/assets/main-O6a-DCWF.js"></script>
<link rel="stylesheet" crossorigin href="/assets/main-es_E5H-H.css">
</head>
<body>
+18 -18
View File
@@ -2,30 +2,30 @@ package main
import "testing"
func TestListInboxNodesReturnsOnlyUnassignedRoots(t *testing.T) {
func TestListInboxNodesReturnsOnlyCapturedArtifacts(t *testing.T) {
app, _ := setupTestApp(t)
unassigned, err := app.CreateNodeFromTemplate("", "Unassigned Root", "folder.default")
manual, err := app.CreateNodeFromTemplate("", "Manual Root", "folder.default")
if err != nil {
t.Fatalf("create unassigned root: %v", err)
t.Fatalf("create manual root: %v", err)
}
inbox, err := app.CreateNodeFromTemplate("", "Inbox Root", "folder.default")
legacyInbox, err := app.CreateNodeFromTemplate("", "Legacy Inbox Root", "folder.default")
if err != nil {
t.Fatalf("create inbox root: %v", err)
t.Fatalf("create legacy inbox root: %v", err)
}
assigned, err := app.CreateNodeFromTemplate("", "Assigned Root", "folder.default")
captured, err := app.CreateNodeFromTemplate("", "Captured Artifact", "folder.default")
if err != nil {
t.Fatalf("create assigned root: %v", err)
t.Fatalf("create captured artifact: %v", err)
}
child, err := app.CreateNodeFromTemplate(unassigned.ID, "Nested Child", "folder.default")
child, err := app.CreateNodeFromTemplate(captured.ID, "Nested Child", "folder.default")
if err != nil {
t.Fatalf("create child: %v", err)
}
if _, err := app.db.Exec(`UPDATE nodes SET section = 'inbox' WHERE id = ?`, inbox.ID); err != nil {
t.Fatalf("mark inbox: %v", err)
if _, err := app.db.Exec(`UPDATE nodes SET section = 'inbox' WHERE id = ?`, legacyInbox.ID); err != nil {
t.Fatalf("mark legacy inbox: %v", err)
}
if _, err := app.db.Exec(`UPDATE nodes SET section = 'projects' WHERE id = ?`, assigned.ID); err != nil {
t.Fatalf("mark assigned: %v", err)
if err := app.nodes.MetaSet(captured.ID, "capture.inbox", "true"); err != nil {
t.Fatalf("mark captured: %v", err)
}
list, err := app.ListInboxNodes()
@@ -37,14 +37,14 @@ func TestListInboxNodesReturnsOnlyUnassignedRoots(t *testing.T) {
for _, item := range list {
got[item.ID] = true
}
if !got[unassigned.ID] {
t.Fatal("unassigned root missing from inbox")
if !got[captured.ID] {
t.Fatal("captured artifact missing from inbox")
}
if !got[inbox.ID] {
t.Fatal("section=inbox root missing from inbox")
if got[manual.ID] {
t.Fatal("manual root should not be in inbox")
}
if got[assigned.ID] {
t.Fatal("assigned root should not be in inbox")
if got[legacyInbox.ID] {
t.Fatal("section=inbox root without capture metadata should not be in inbox")
}
if got[child.ID] {
t.Fatal("nested child should not be in inbox")
-1
View File
@@ -2004,7 +2004,6 @@
<h2>{t('nav.inbox')}</h2>
<p>{t('inbox.subtitle')}</p>
</div>
<button class="btn btn-primary btn-sm" on:click={openCreateRoot}>+ {t('nav.createNode')}</button>
</div>
{#if inboxNodes.length === 0}
<div class="empty-state">
+1 -1
View File
@@ -24,7 +24,7 @@ export default {
'nav.createInside': 'Create inside',
'nav.createNode': 'Create element',
'nav.moveToRoot': 'Move to root',
'inbox.subtitle': 'Root items without an assigned section',
'inbox.subtitle': 'Captured materials that still need to be assigned to cases',
'inbox.empty': 'No unprocessed items',
'trash.openFolder': 'Open trash folder',
'trash.empty': 'Trash is empty',
+1 -1
View File
@@ -25,7 +25,7 @@ export default {
'nav.createNode': 'Создать элемент',
'nav.moveToRoot': 'Переместить в корень',
'inbox.subtitle': 'Корневые элементы без назначенного раздела',
'inbox.subtitle': 'Захваченные материалы, которые нужно разложить по делам',
'inbox.empty': 'Неразобранных элементов нет',
'trash.openFolder': 'Открыть папку корзины',
+5 -2
View File
@@ -169,10 +169,13 @@ func (r *Repository) ListRoots(includeDeleted bool) ([]Node, error) {
return scanNodes(rows)
}
// ListInboxRoots returns active root nodes that are not assigned to a specific section.
// ListInboxRoots returns active root capture artifacts explicitly marked for inbox.
func (r *Repository) ListInboxRoots(includeDeleted bool) ([]Node, error) {
q := `SELECT ` + nodeColumns + ` FROM nodes
WHERE parent_id IS NULL AND COALESCE(section, '') IN ('', 'inbox')`
WHERE parent_id IS NULL
AND id IN (
SELECT node_id FROM node_meta WHERE key = 'capture.inbox' AND value = 'true'
)`
if !includeDeleted {
q += " AND deleted_at IS NULL"
}
+5 -3
View File
@@ -135,7 +135,8 @@ async function runReadyScenario(cdp, url) {
await clickText(cdp, '.nav-item', 'Неразобранное')
await assertText(cdp, 'Неразобранное', 'inbox: system view opens')
await assertText(cdp, 'Inbox Smoke Item', 'inbox: unassigned item visible')
await assertText(cdp, 'Inbox Smoke Item', 'inbox: captured item visible')
await assertEval(cdp, `!document.querySelector('.inbox-screen')?.innerText.includes('Manual Root Item')`, 'inbox: manual root is hidden')
await screenshot(cdp, 'inbox.png')
await clickText(cdp, '.inbox-item-actions .btn', 'Открыть')
await assertText(cdp, 'Inbox Smoke Item', 'inbox: item opens from list')
@@ -577,7 +578,8 @@ function wailsMockSource() {
],
},
{ id: 'node-client', title: 'Smoke Client', type: 'client', section: 'clients', createdAt: now, has_children: false, children: [] },
{ id: 'node-inbox', title: 'Inbox Smoke Item', type: 'folder', section: '', createdAt: now, has_children: false, children: [] },
{ id: 'node-inbox', title: 'Inbox Smoke Item', type: 'folder', section: '', captureInbox: true, createdAt: now, has_children: false, children: [] },
{ id: 'node-manual-root', title: 'Manual Root Item', type: 'folder', section: '', createdAt: now, has_children: false, children: [] },
],
notes: {
'node-project': [{ id: 'note-1', title: 'Smoke note', createdAt: now }],
@@ -685,7 +687,7 @@ function wailsMockSource() {
],
ListWorkspaceTree: async () => clone(state.nodes),
ListWorkspaceChildren: async (id) => clone(childrenOf(id)),
ListInboxNodes: async () => clone(state.nodes.filter((node) => !node.parent_id && (!node.section || node.section === 'inbox'))),
ListInboxNodes: async () => clone(state.nodes.filter((node) => !node.parent_id && node.captureInbox === true)),
ListTrash: async () => clone({
trashPath: '/tmp/verstak-smoke-vault/.verstak/trash',
nodes: [{ id: 'node-trash', title: 'Trash Smoke Folder', type: 'folder', fsPath: 'Trash Smoke Folder', deletedAt: now }],