PhoenixKitKnowledgeBase (PhoenixKitKnowledgeBase v0.1.0)

Copy Markdown View Source

An Obsidian-style linked knowledge base for PhoenixKit, backed by real markdown files.

Admins open Knowledge Base in the sidebar, create a vault, and write markdown notes in it. Notes live in a folder tree, carry tags, and link to one another with [[wiki-links]]; each note shows what links back to it, and the vault keeps a running list of everything linked to but not yet written.

Files are the source of truth

A vault is a directory. A folder is a directory. A note is a <Title>.md file — the filename is the title, exactly as in Obsidian. So rg, git, pandoc, vim and Obsidian itself all work on the tree directly, and nothing you write is trapped in a database.

<root>/system/<vault-slug>/…                 organisation vaults
<root>/users/<email-slug>/<vault-slug>/…     personal vaults

Postgres is an index of those files: cached content for full-text search, parsed front matter, tags, and the whole link graph. Delete the tables and a rescan rebuilds every bit of it from the markdown.

Keeping the index honest, without a watcher

There is no filesystem watcher. Edits made through the web UI index themselves; edits made anywhere else are picked up by Rescan — the button on a vault, or mix phoenix_kit_knowledge_base.rescan from a shell or a cron job. A rescan compares (mtime, size) first and only hashes what looks changed, and it recognises a moved file by content hash so an external mv keeps the note's uuid, its permalink and every backlink into it.

Two things follow from having no watcher, and both are handled rather than hoped about:

  • The web UI never clobbers an external edit. Every save first compares the file's hash with the indexed one and refuses if they differ, offering to reload or to overwrite deliberately.
  • Titles are not unique. The filesystem allows research/Ideas.md and archive/Ideas.md, so the index describes that rather than rejecting it. [[Ideas]] resolves to a note in the same folder first, then the shortest path; [[research/Ideas]] says exactly which one you meant.

Two browsers, two people

Every vault page subscribes to a PubSub topic for that vault, so a note added, renamed, saved or deleted anywhere in the running application — the Rescan button included — reaches every open session. mix phoenix_kit_knowledge_base.rescan is the exception: it runs in its own OS process, with no PubSub, so open pages see its changes on their next navigation. Run PhoenixKitKnowledgeBase.Rescan.run/1 on the live node (a release's rpc) when they should refresh at once. A note being read is refreshed in place; a note being edited raises a banner instead, because the buffer may hold unsaved keystrokes.

Concurrent edits to the same note are detected, not merged: the second writer is stopped by the stale-write guard and given an explicit choice between reloading and overwriting. Two people on different notes in a shared vault is the case this supports well; two people in the same note at the same time wants a CRDT, which this is not.

Vaults

A vault is the namespace links resolve in — [[Foo]] never crosses a vault boundary.

  • System vaults are org-wide, created by anyone holding knowledge_base.manage_system_vaults, who are implicitly owners of all of them.
  • Personal vaults belong to one person. Nobody else sees a personal vault in the app — not even an org Owner — unless its owner adds them as a member.

Both share one membership model (owner / editor / viewer), and PhoenixKitKnowledgeBase.Vaults is the single place that decides any of it.

Note the limit of that, plainly: membership is enforced in the application. The notes are files, so anyone with shell access to the server can read every vault. That is inherent to storing notes as files, not a gap to be closed here.

Zero host setup

The editor is Leaf — the same one PhoenixKit uses elsewhere — in its Obsidian-style hybrid live-preview mode, with [[ and # inline-suggestion triggers. Rendering goes through core's MDEx + sanitizer, so this module ships no JavaScript and asks the host for nothing beyond the dependency and a root path.

Note how Leaf's own bundle arrives: core's vendored phoenix_kit.js registers a placeholder hook that lazy-loads leaf.js from jsDelivr on first mount. That is core's mechanism, but it means the browser must be able to reach the CDN and a strict script-src CSP must allow it. Loading a vendored leaf.js before app.js short-circuits the loader. css_sources/0 is declared so a host running core's Tailwind source compiler picks up the classes used here.

Installation

# host mix.exs
{:phoenix_kit_knowledge_base, "~> 0.1"}

# host config
config :phoenix_kit_knowledge_base,
  root: Path.expand("../priv/knowledge_base", __DIR__)

Then mix deps.get and mix phoenix_kit.update (creates the five phoenix_kit_kb_* index tables). Enable it on the admin Modules page.

To adopt notes you already have, move or symlink the directory into place under the root, create a vault with the matching slug, and press Rescan.

Summary

Functions

The registry and dynamic supervisor behind live editing.

Tailwind scans this app's templates for the classes the knowledge base UI uses.

Whether the module is on.

Config for the admin Modules page.

The phoenix_kit_kb_* table coordinator (run by mix phoenix_kit.update).

The base knowledge_base permission opens the section; the sub-permission decides who may create and administer system vaults.

Multi-page routing (vaults, workspace, members) — see PhoenixKitKnowledgeBase.Routes.

Functions

children()

The registry and dynamic supervisor behind live editing.

Core starts these under its own supervisor, so the host needs no setup. They start whether or not this module is enabled — children/0 is collected statically, before anything can ask the database — but an empty registry and an idle DynamicSupervisor cost nothing until somebody opens a note.

See PhoenixKitKnowledgeBase.Collab.

css_sources()

Tailwind scans this app's templates for the classes the knowledge base UI uses.

enabled?()

Whether the module is on.

Rescues and returns false because this is called before migrations have necessarily run — a settings table that doesn't exist yet must read as "disabled", not crash host startup.

get_config()

Config for the admin Modules page.

Only enabled?/0 — this runs on every render of that page, and counting vaults or notes would add a query per page load for a number nobody reads there.

migration_module()

The phoenix_kit_kb_* table coordinator (run by mix phoenix_kit.update).

permission_metadata()

The base knowledge_base permission opens the section; the sub-permission decides who may create and administer system vaults.

Note what the sub-permission deliberately does not grant: access to other people's personal vaults. See PhoenixKitKnowledgeBase.Vaults.

route_module()

Multi-page routing (vaults, workspace, members) — see PhoenixKitKnowledgeBase.Routes.