This is the canonical developer guide for building supertag plugins.
Core principles:
- Org owns Document Facts; the database owns Semantic Facts and physically contains rebuildable Projections.
- Plugins primarily extend views (any UI), not schemas.
- Plugins MUST read data through the UI-agnostic View Data API (
supertag-view-api.el). - Writes go through ops APIs and (usually)
supertag-with-transaction.
Working example plugin:
doc/examples/supertag-view-demo-dashboard.el
Chinese version:
doc/SUPERTAG-PLUGIN-GUIDE_cn.md
supertag currently stores Semantic Facts, Document Projections, and derived
state in one central hash-table Store. The Store is a physical container, not
the owner of every fact in it. See doc/OWNERSHIP-CONSTITUTION_cn.md.
Common collections and their types:
:nodes— node entities (plist):tags— tag entities (plist):relations— relation entities (plist):field-definitions,:tag-field-associations,:field-values— global field model
- Entities are plists (property lists), usually containing
:idplus other keys. - Entity IDs are strings.
- Keys are keywords (e.g.
:title,:file,:tags).
- Read APIs return plists and MUST be treated as immutable snapshots by plugin code.
- Document Fact writes go through document commands; Semantic Fact writes go through the matching ops function. Both emit the events needed by views.
The View Data API is internal public and UI-agnostic. Use it for all data reads in plugins (even if you do not use table UI).
File:
supertag-view-api.el
Many read APIs take a QUERY-SPEC plist, e.g.:
(:type :tag :value "foo")→ nodes that have tag "foo"(:type :nodes)→ all node IDs(:type :tags)→ all tag IDs
Dataset
-
(supertag-view-api-list-tags) -> (list string)
All tag names (sorted). -
(supertag-view-api-tag-id TAG-NAME) -> string-or-nil
Tag name → tag id. -
(supertag-view-api-list-entity-ids QUERY-SPEC) -> (list string)
Entry point to get IDs for a dataset.
Entity fetch
-
(supertag-view-api-get-entity TYPE ID) -> plist-or-nil
Fetch one entity.TYPEsupports aliases like:node/:nodes,:tag/:tags, etc. -
(supertag-view-api-get-entities TYPE IDS) -> (list plist)
Batch fetch (recommended for performance).
Raw collections (legacy compatibility)
(supertag-view-api-get-collection COLLECTION) -> hash-table
Transitional legacy Interface that returns an underlying Store collection. Do not use it in new plugins. Use a specific query helper; if none exists, add the smallest domain query to the existing query Module. Removal is tracked by ownership-separationtask026.
Field access
(supertag-view-api-node-field-in-tag NODE-ID TAG-ID FIELD-NAME) -> value
Read a node field value within a tag context (field values).
Subscription
(supertag-view-api-subscribe EVENT FN) -> unsubscribe-fn
Subscribe to changes.EVENTis a keyword like:node-updated/:store-changedor a store path list. Returns anunsubscribe-fnyou should call on cleanup.
Plugins MUST NOT mutate the raw Store. Use document commands for Document Facts and ops functions for Semantic Facts.
Most write flows should be wrapped in:
(supertag-with-transaction
;; multiple ops here
...)This batches notifications and makes the UI react once per logical change.
Nodes
(supertag-node-create PROPS) -> node-plist(supertag-node-update NODE-ID UPDATER) -> node-plist-or-nil(supertag-node-delete NODE-ID) -> deleted-node-or-nil
Tags
(supertag-tag-create PROPS) -> tag-plist(supertag-tag-update TAG-ID UPDATER) -> tag-plist-or-nil(supertag-tag-delete TAG-ID) -> deleted-tag-or-nil(supertag-tag-add-field TAG-ID FIELD-DEF) -> tag-plist(supertag-tag-remove-field TAG-ID FIELD-NAME) -> tag-plist
Fields
(supertag-field-set NODE-ID TAG-ID FIELD-NAME VALUE) -> VALUE(supertag-field-set-many NODE-ID SPECS) -> plist
SPECSis a list of(:tag-id TAG-ID :field FIELD-NAME :value VALUE)items.
Relations
(supertag-relation-add-reference FROM-ID TO-ID) -> t-or-nil(Store-only; never writes Org)(supertag-relation-delete RELATION-ID) -> deleted-relation-or-nil
Data conventions for writes:
- IDs are strings.
- UPDATER functions receive the current plist and return the updated plist (or nil to abort).
- Prefer calling ops functions inside a transaction when you do multiple writes.
supertag allows users to register/override schemas at init time. This is intended for advanced setups (custom entities or extended validation), and does not provide automatic migrations.
Recommended configuration pattern:
(setq supertag-schema-registration-functions
(list
(lambda ()
;; Override/extend an existing schema (merge by default).
(supertag-schema-register :node '(:my-field (:type :string :default "")))
;; Or register a brand new entity type + schema.
(supertag-register-entity-type
:my-entity
'(:id (:type :string :required t)
:name (:type :string :default "")))))))File:
supertag-core-transform.el
API:
(supertag-with-transaction ...)
Semantics:
- It suppresses notifications during the body and emits a batch of changes after the body finishes.
- It provides
supertag--transaction-activeand collects a transaction log. - It currently focuses on notification batching; do not assume full rollback semantics unless explicitly implemented in code.
File:
doc/examples/supertag-view-demo-dashboard.el
It demonstrates:
- reading node IDs via
(supertag-view-api-nodes-by-tag TAG) - batch fetching nodes via
(supertag-view-api-get-entities :nodes IDS) - subscribing to
:node-updatedand refreshing with throttling - rendering a custom buffer with clickable entries
- Load the demo:
(add-to-list 'load-path "/path/to/supertag/doc/examples/")
(require 'supertag-view-demo-dashboard)- Open:
M-x supertag-view-demo-dashboard-open
- Verify:
- dashboard shows node count and a clickable list
RETjumps to the node- after changing a node, the dashboard auto-refreshes (or press
g)