Commit a69c7d4
authored
feat(workbench): content management — delete KB, delete/edit wiki pages (#198)
* feat(api): delete a knowledge base (POST /api/v1/kb/delete + openkb delete-kb)
Physical, irreversible KB deletion with a type-the-name confirmation.
- config.delete_kb: rmtree the KB directory + unregister it from the global
registry (known_kbs / kb_aliases / default_kb, via the new unregister_kb).
Guards against deleting a non-KB path; tolerates a ghost registry entry
(directory already gone) by just unregistering.
- POST /api/v1/kb/delete: confirm_name must equal kb, re-checked server-side.
Lives in a new api_kbs_router (sibling of the config/graph/output routers) so
api.py stays under the 800-line gate; delete_kb self-locks (config lock), no
create_app closure needed.
- openkb delete-kb NAME: type-the-name prompt (or --yes) then delete.
Tests: endpoint (success + unregister, confirm mismatch, non-KB target) in
test_api.py; config (physical delete + unregister, refuse non-KB, ghost
tolerance) in test_config.py.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
* feat(api): delete a wiki page (POST /api/v1/page/delete) with backlink impact
Delete a concept/entity page and degrade references cleanly instead of leaving
them dangling:
- page_ops.delete_wiki_page: dry_run reports the backlink pages (whose inbound
[[wikilinks]] would be demoted) without touching anything; execute removes the
page under the KB ingest lock, strips its index.md entry outright
(compiler.remove_doc_from_index — the entry is a [[link]] line), and demotes
the now-dangling inbound [[links]] to plain text in ONLY those backlink pages
(lint.fix_broken_links restrict_to). The page ref is "section/stem", validated
to concepts/ or entities/ only (path-traversal-safe).
- New api_pages_router hosts POST /api/v1/page (read — relocated here from
api.py to keep it under the 800-line gate) + POST /api/v1/page/delete.
Tests: dry-run impact + execute (page gone, inbound link demoted to text, index
entry removed, sibling kept), 404 on missing page, and rejection of unsafe /
non-editable refs.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
* feat(api): edit a wiki page (PUT /api/v1/page) + link context (POST /api/v1/page/links)
- page_ops.edit_wiki_page: replace a concept/entity page's BODY while preserving
its code-managed OKF frontmatter (type/description/sources) verbatim; any
frontmatter in the submitted content is dropped. Dead [[wikilinks]] the user
typed are demoted to plain text on save (OpenKB keeps the wiki broken-link-free)
and returned as ghosts_stripped so the UI can warn. Atomic write under the KB
ingest lock.
- page_ops.page_link_context (POST /api/v1/page/links): outbound + inbound links
for the edit-impact panel. Editing the body does not break either (links are
path-based), so this is context, not a blocker — the honest impact model.
- PUT /api/v1/page added to api_pages_router.
Tests: edit preserves frontmatter + keeps a resolvable link + demotes a dead one
(reports ghosts_stripped) + replaces the body; 404 on a missing page; links
reports out/backlinks.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
* feat(web): delete a knowledge base from the settings sheet (type-name confirm)
Adds a Danger Zone section to KbSettingsSheet: a type-the-name confirmation
gates deleteKb() (POST /api/v1/kb/delete); on success KbDetail navigates back
to the KB list. New deleteKb client + kbSettings danger* keys + common cancel
(zh + en, identical key sets).
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
* feat(web): in-reader page edit + delete with impact preview (F2/F3)
For an open concept/entity page, the reader header now shows Edit and Delete:
- Delete: a dry-run first (deletePage dryRun) surfaces the backlink pages whose
[[links]] will demote to plain text; a red confirm card lists them, then the
real delete closes the page and refreshes the inventory.
- Edit: a body textarea (frontmatter is preserved server-side), Save via
PUT /api/v1/page, a links panel (getPageLinks: out/backlinks), a "recompile
may overwrite" note, and a toast listing any dead links demoted to text.
Adds the deletePage/editPage/getPageLinks wiki clients and kb:pageOps.* locale
keys (zh + en, identical key sets). Build green (i18n guard + tsc + vite).
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
* fix(workbench): address code-review findings (content-mgmt correctness + concurrency)
Backend (page_ops / kb_admin / lint):
- delete/edit now do their read-modify-write INSIDE the KB ingest lock and
re-check the page exists under it: no stale backlink snapshot, no resurrecting
a concurrently-deleted page, no 500 on a racing unlink (missing_ok=True). [#4,#5,#6]
- delete_kb serializes rmtree under the ingest lock (was fully unlocked). Moved
delete_kb/unregister_kb into a new openkb/kb_admin.py so config.py drops back
under the per-file line gate (751 lines). [#1,#15]
- page deletion no longer scans or rewrites lint's _EXCLUDED_FILES
(AGENTS.md/SCHEMA.md/log.md); fix_broken_links(restrict_to) also refuses them,
which protects `openkb remove` too. [#2]
- edit_wiki_page treats `content` as the body verbatim — no naive frontmatter
split that silently dropped a body opening with a '---' block. [#3]
- delete uses the real on-disk stem for the exact-match index-entry removal
(case-insensitive FS), and adds index.md to the demotion set so a [[target]]
embedded in another entry's brief no longer dangles. [#7,#9]
API / CLI:
- delete-KB endpoint & CLI can now clean a ghost registry entry (registered name
whose directory was removed by hand), catch delete_kb's ValueError -> 400, and
the endpoint is typed `-> KbDeleteResponse`. [#8,#13,E5]
Frontend (KbDetail):
- Deleting a KB dispatches `openkb:reload-kbs` so the sidebar refreshes
immediately (was stale until a manual reload) — live-test fix.
- Removed the dead `pageReloadSeq` path (editPage always returns content). [#14]
Tests: excluded-doc-untouched, '---'-body preservation, ghost-KB unregister;
non-KB-target test updated to the new resolution model. Backend 1234 passed,
mypy/ruff clean, frontend build green.
Not changed (intentional): pages_linking_to is NOT folded into build_graph (that
excludes explorations/ backlinks) [#10]; the preview+confirm delete inherently
scans per request [#11]; EDITABLE_SECTIONS stays duplicated in the frontend,
which can't import the Python constant [#12].
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
* feat(workbench): allow editing summary pages (edit ⊇ delete section sets)
Summaries are compiled markdown like concepts/entities (a recompile regenerates
them), so they are now editable via PUT /api/v1/page. They remain NOT
independently deletable — a summary is removed by deleting its source document.
Split page_ops into EDITABLE_SECTIONS (concepts/entities/summaries) vs
DELETABLE_SECTIONS (concepts/entities); validate_page_ref takes the allowlist.
Frontend ReaderBody gates Edit on canEdit (incl. summaries), Delete on canDelete.
Test: summary editable (frontmatter preserved) + summary delete rejected (400).
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
* fix(api): cross-platform delete_kb + endpoint OSError handling (xhigh review #1,#2)
- delete_kb no longer holds the ingest lock DURING rmtree (the lock file lives
inside the KB dir; Windows cannot delete an open file — the prior review-fix
regressed this). It now takes the lock as a BARRIER (drain + wait out any
in-flight mutation), releases it, re-checks existence, then rmtrees. [#1]
- delete-KB endpoint maps FileNotFoundError to an idempotent success (a
concurrent delete already removed the tree) and other OSError to a clean 500
with a message, instead of an uncaught 500 stack trace. [#2]
Tests: endpoint OSError -> clean 500, FileNotFoundError -> 200 deleted.
Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG1 parent 9036c2c commit a69c7d4
20 files changed
Lines changed: 1425 additions & 29 deletions
File tree
- frontend/src
- api
- components
- locales
- en
- zh
- pages
- openkb
- tests
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
41 | 41 | | |
42 | 42 | | |
43 | 43 | | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
44 | 51 | | |
45 | 52 | | |
46 | 53 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
52 | 52 | | |
53 | 53 | | |
54 | 54 | | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
11 | | - | |
| 11 | + | |
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
| |||
28 | 28 | | |
29 | 29 | | |
30 | 30 | | |
| 31 | + | |
31 | 32 | | |
32 | 33 | | |
33 | 34 | | |
34 | 35 | | |
35 | 36 | | |
36 | 37 | | |
| 38 | + | |
37 | 39 | | |
38 | 40 | | |
39 | 41 | | |
| |||
114 | 116 | | |
115 | 117 | | |
116 | 118 | | |
| 119 | + | |
117 | 120 | | |
118 | 121 | | |
119 | 122 | | |
| |||
732 | 735 | | |
733 | 736 | | |
734 | 737 | | |
| 738 | + | |
| 739 | + | |
| 740 | + | |
| 741 | + | |
| 742 | + | |
| 743 | + | |
| 744 | + | |
| 745 | + | |
| 746 | + | |
| 747 | + | |
| 748 | + | |
| 749 | + | |
| 750 | + | |
| 751 | + | |
| 752 | + | |
| 753 | + | |
| 754 | + | |
| 755 | + | |
| 756 | + | |
| 757 | + | |
| 758 | + | |
| 759 | + | |
| 760 | + | |
| 761 | + | |
| 762 | + | |
| 763 | + | |
| 764 | + | |
| 765 | + | |
| 766 | + | |
| 767 | + | |
| 768 | + | |
| 769 | + | |
| 770 | + | |
| 771 | + | |
| 772 | + | |
| 773 | + | |
| 774 | + | |
| 775 | + | |
| 776 | + | |
| 777 | + | |
| 778 | + | |
| 779 | + | |
| 780 | + | |
| 781 | + | |
| 782 | + | |
| 783 | + | |
| 784 | + | |
| 785 | + | |
| 786 | + | |
| 787 | + | |
| 788 | + | |
| 789 | + | |
| 790 | + | |
| 791 | + | |
| 792 | + | |
| 793 | + | |
| 794 | + | |
| 795 | + | |
| 796 | + | |
| 797 | + | |
| 798 | + | |
| 799 | + | |
| 800 | + | |
| 801 | + | |
| 802 | + | |
| 803 | + | |
| 804 | + | |
| 805 | + | |
| 806 | + | |
| 807 | + | |
| 808 | + | |
| 809 | + | |
| 810 | + | |
| 811 | + | |
| 812 | + | |
| 813 | + | |
| 814 | + | |
| 815 | + | |
| 816 | + | |
| 817 | + | |
| 818 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
10 | | - | |
| 10 | + | |
| 11 | + | |
11 | 12 | | |
12 | 13 | | |
13 | 14 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
46 | 46 | | |
47 | 47 | | |
48 | 48 | | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
49 | 73 | | |
50 | 74 | | |
51 | 75 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
38 | 38 | | |
39 | 39 | | |
40 | 40 | | |
41 | | - | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
42 | 49 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
10 | | - | |
| 10 | + | |
| 11 | + | |
11 | 12 | | |
12 | 13 | | |
13 | 14 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
46 | 46 | | |
47 | 47 | | |
48 | 48 | | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
49 | 73 | | |
50 | 74 | | |
51 | 75 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
38 | 38 | | |
39 | 39 | | |
40 | 40 | | |
41 | | - | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
42 | 49 | | |
0 commit comments