fix: restrict inbox to captured artifacts

This commit is contained in:
mirivlad 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

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>

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")

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">

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',

View File

@ -25,7 +25,7 @@ export default {
'nav.createNode': 'Создать элемент',
'nav.moveToRoot': 'Переместить в корень',
'inbox.subtitle': 'Корневые элементы без назначенного раздела',
'inbox.subtitle': 'Захваченные материалы, которые нужно разложить по делам',
'inbox.empty': 'Неразобранных элементов нет',
'trash.openFolder': 'Открыть папку корзины',

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"
}

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 }],