Bases Plugin (@lapis-notes/bases)
Source: packages/plugins/plugin-bases/src/
Package: @lapis-notes/bases. Manifest: id bases, version 0.0.1, min app version 1.7.7.
The bases package owns structured data views over Markdown files and opens .bases and .base documents through BasesViewType. The package now also mounts the same query-and-view pipeline into markdown-owned read-only surfaces: .base and .bases file embeds render inline through the markdown embed registry, and fenced base or bases code blocks render the fence body as an inline read-only Bases document.
Data Model
type BasesPropertyType = "note" | "formula" | "file";
type BasesPropertyId = string | `${BasesPropertyType}.${string}`;
type SortColumn = { property: string; direction: "ASC" | "DESC" };
type FilterLine = { column: string; op: string; value: unknown };
type BasesDocument = {
filters: unknown;
properties: unknown[];
formulas: unknown[];
activeView: string;
views: BasesView[];
};
Table views track column sizing, table/card layout, row height, card size, image configuration, image fit, and image aspect ratio.
Query And Rendering
The plugin integrates PEaQL, but QueryController no longer builds every candidate row directly from metadataCache.fileCache up front. It first asks app.appDatabase.queryIndexedMetadata() for a plugin-neutral indexed metadata row set, lowering only conservative conjunctive constraints such as folder prefixes, file extensions, tag requirements, property existence, scalar property comparisons, and supported file or property sorts. Returned rows are materialized back into VaultRecord objects before PEaQL executes over the BasesTable database context, so the renderer still owns final query semantics while AppDatabase can narrow and order the candidate set in worker or native SQLite-backed runtimes. When the AppDatabase query path is unavailable or fails, Bases falls back to the previous metadata-cache-derived renderer snapshot.
Indexed-Metadata Pushdown
Current Bases documents do not compile the full filter or formula surface into AppDatabase. The current pushdown boundary is:
| Bases query shape | Lowered into AppDatabase.queryIndexedMetadata() | Notes |
|---|---|---|
file.inFolder(...) | Yes | Becomes pathPrefixes. |
file.hasTag(...) | Yes | Becomes requiredTags. |
file.hasProperty(...) and !hasProperty(...) | Yes | Becomes property existence checks. |
file.ext = ... | Yes | Only direct extension equality is lowered. |
note.foo =, !=, >, >=, <, <= and the same unprefixed property ids | Yes | Only scalar property comparisons are lowered. |
Sort by file.path, file.ext, file.mtime, file.size, or a direct property | Yes | limit is only pushed down when every active sort lowers successfully. |
OR groups, non-conjunctive filters, custom filter strings, and custom filter lines | No | The lowering pass only walks explicit and groups. |
File predicates outside the subset above, such as file.name comparisons | No | These stay renderer-owned even though Bases still evaluates them. |
Link predicates such as file.hasLink(...) | No | The indexed metadata contract can carry resolved link targets, but the current Bases lowering does not emit them. |
| Formulas, group-by behavior, view search, and custom view logic | No | These continue to run after candidate rows are materialized back into VaultRecords. |
Unsupported or unlowered clauses do not disable Bases queries. They simply are not pushed into AppDatabase, and PEaQL still applies the full Bases semantics in the renderer over the loaded candidate rows.
Explicit note.* properties declared in a Bases document and observed frontmatter keys inferred from rows are both materialized into the structured note.* query schema, which keeps generated plugin-owned .base documents and raw predicates like note.status compiling against the structured note object.
Rendered views include a TanStack Table data grid with sizing, sorting, filtering, visibility, and cell editing, plus a card/grid renderer with configurable image handling. Card image fields resolve string, wikilink/embed, markdown-image, and file values to vault image files and render them through Vault.getResourceUrl(), so Electron uses the scoped native resource protocol while browser vaults keep blob URL fallback behavior. Toolbar filter state, counts, and popover rows reflect both document-wide filters and view-local view.filter predicates, including structured filter-line objects such as inFolder rules stored in .base documents. Metadata invalidation is owned by QueryController rather than by each child renderer: a base now reacts once per metadata event and uses conservative dependency tracking over the current filters, visible or sorted properties, group-by state, and direct file-reference neighborhood to decide whether it needs to rerun the query. Static file-path fields such as file.name and inFolder no longer force a vault-wide reload on unrelated metadata changes, while mutable file fields such as file.mtime, custom formulas, and direct-link neighborhoods still keep the broader conservative fallback. The Sort popover and table column headers now both use @dnd-kit/svelte drag handles, so Bases keeps one drag stack for sort-clause and column-order reordering while persisting updated view.order values back into the .base document. The built-in Bases view registry also merges external view registrations supplied through Plugin.registerBasesView() so other plugins can contribute Bases-specific layouts through a public runtime path. Custom view registrations now persist across plugin startup order, and the Bases document normalizer preserves non-built-in view type ids when .base files round-trip through source mode, so generated plugin-owned task views can reopen without collapsing to the unknown-view fallback. Markdown-hosted embeds and fenced Bases blocks intentionally mount the same renderer in a read-only configuration: they reuse query execution, sorting, grouping, and rendering, but they do not write source document edits back through the vault or expose the file-view source/preview mode switcher.
Tag cells use the shared @lapis-notes/ui/tag renderer so Bases tag chips match Markdown preview tags and metadata property chips.
The shared Bases view-settings panel renders labeled controls for plugin-registered dropdown, slider, property, text, and toggle options, so custom .base views can expose editable per-view configuration directly from the sidebar. Filled check indicators in Bases popovers use --interactive-accent with --text-on-accent so selected property, group, sort, and visibility controls keep readable foreground contrast under custom themes.
CSS Ownership
The package follows the Plugin CSS Contract with workspace dist/app.css, standalone dist/styles.css, and bases-... selectors across the shell, toolbar, table, card/list, summary, and error surfaces.