Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Bases Parity Audit

This note audits the current Bases implementation in this repository against two upstream references:

  • Obsidian API types from obsidian.d.ts v1.12.3
  • Obsidian help pages for Bases, Views, Syntax, Functions, and Formulas

The audit covers two local surfaces:

  • the shared compatibility API in packages/api/src/lib/bases.ts
  • the working runtime in packages/plugins/plugin-bases/src/

The main finding is that the repository has partial Bases coverage, but parity is split across two different implementations:

  • @lapis-notes/api exports a compatibility-oriented Bases surface with several type and helper shims
  • plugin-bases owns the actual .base and .bases runtime, query execution, toolbar, and built-in views

That split means the repo currently has name-level parity in some places without behavior-level parity.

Progress since the initial audit

The first parity follow-up slice has landed since this note was originally written.

Completed work:

  • the shared API now carries the richer DateValue and DurationValue helpers that had previously lived only in the plugin-private model, including parsing, relative/date-only helpers, duration math, and millisecond conversion
  • the shared config and option surface now includes formulas, summaries, groupBy, recursive filter unions, and the upstream-style options?: (config) => ... callback shape
  • plugin-bases now consumes shared BasesPropertyId, shared BasesViewConfig, and shared view option types in more of the runtime surface instead of maintaining separate local copies
  • the function gap list is now backed by an executable plugin test in src/bases-view/functions-parity.spec.ts, which locks in the currently supported names and makes regressions visible immediately
  • plugin-bases now has a dedicated vitest.config.ts, which makes these TypeScript-only parity checks runnable without bringing up the package’s Svelte/Vite browser test path
  • the runtime now exposes additional file-derived metadata columns for file.links, file.embeds, file.backlinks, and file.properties, backed by focused tests around the shared derivation logic
  • the runtime now threads groupBy into its query result model so groupedData is populated when a Base view config includes a grouping key, backed by focused tests around the grouping helper, and the query generator now also projects the active group key even when that property is hidden from the table order so grouped renderers do not collapse into Ungrouped
  • the plugin now registers a built-in list view and includes a grouped list renderer with compact unordered-list rows built from the selected columns, reducing the built-in view gap to map
  • the plugin now also registers a first-class map view with an explicit unsupported-state renderer, so all documented built-in layout names are present even though real map capability is still missing
  • the source-to-preview document normalization path now preserves first-class cards views instead of dropping them during YAML reloads, so newly added card views survive mode switches
  • grouped cards views now keep their section headers sticky within the scrolling cards pane, matching the existing grouped list/table behavior more closely
  • grouped list and cards sections can now be folded from their sticky headers using local per-view collapse state
  • grouped table views can now also be folded from either the inline group row or the sticky active-group header while keeping the virtualized table body in sync
  • the documented file-property list is now fully exposed in the runtime, including the explicit file.file self-reference field
  • the shared API now implements BasesView.createFileForView() using the existing file-manager path allocation, optional frontmatter processing, and file-opening flow
  • the plugin toolbar now surfaces create-new-file through a direct New button, while copy/export CSV actions live in the result-limit popover backed by the same createFileForView() and CSV serialization helper flows
  • preview-mode Bases config edits now write the serialized .base document back to the underlying file immediately instead of waiting on the editor’s debounced autosave path, which keeps newly added views from disappearing on quick reloads
  • the plugin toolbar now exposes a search toggle that opens a search panel above the active view while filtering the current result set across the displayed properties, the toolbar result count now continues to reflect the underlying query result size instead of the transient search subset, and the search input now keeps the controller query state synchronized through focus changes and scrolling in the virtualized table layout while surfacing the active match count and a clear-search action inline
  • the table layout now applies base-level and per-view summary config in a footer row, backed by a focused summary-resolution helper test, and the shared width map now keeps headers and body cells aligned on first render while the trailing visible column absorbs spare horizontal room; active column resizing now keeps the virtualized header and body cells in lockstep while widths shrink or grow, the sticky header background strip now matches the rendered header height, the first row starts directly beneath that header without an extra spacer gap, the first visible row now renders a top border so that junction reads as the header’s bottom edge, table headers now use a visible full-cell hover background only for non-active columns while focused headers keep a stable active background without the old sorted-column ring treatment or the extra header focus shadow, active table searches now keep a sticky in-table query strip while the panel is closed and stay synchronized with the virtualized row body when the filtered result set shrinks or the previous scroll position would otherwise leave stale row indexes behind, and grouped table views render full-width section rows for the active group key with a sticky section label beneath the table header instead of staying flat
  • list, cards, and the map fallback now also expose configured summaries through a compact summary strip
  • the property editor now surfaces base-level and per-view summary selectors so configured summaries are no longer edit-only in raw .base JSON
  • the sort popover now includes a groupBy picker with direction controls above the sort list, and the popover itself scrolls when vertical space is constrained so those controls stay contained within the panel

The remaining parity work is still substantial, but the repo now has an executable checklist and less type/config duplication between @lapis-notes/api and plugin-bases.

Current implementation

Shared API

packages/api/src/lib/bases.ts currently exports the public Bases compatibility layer used by first-party plugins and parity tests. It includes:

  • core identifiers and config types such as BasesPropertyType, BasesPropertyId, BasesConfigFile, BasesViewConfig, and BasesViewRegistration
  • Value wrappers such as BooleanValue, NumberValue, StringValue, DateValue, DurationValue, FileValue, ListValue, and ObjectValue
  • query result helpers such as BasesEntry, BasesEntryGroup, BasesQueryResult, QueryController, parsePropertyId, and toValue

This file is useful as a compatibility surface, and it now owns more of the shared value/config behavior than it did during the initial audit. It is still not the complete source of truth for the running Bases feature, because runtime query execution and view/controller behavior remain plugin-local.

Runtime plugin

packages/plugins/plugin-bases/src/ contains the working Bases feature. Today it provides:

  • a BasesPlugin that registers .base and .bases files to the bases view type
  • a BasesView file view that supports source and preview modes
  • built-in table, cards, list, map, and fallback unknown renderers
  • a query controller that builds rows from metadataCache, frontmatter, and a PEaQL-backed in-memory table
  • toolbar actions for view selection, limit, sort, group, filters, search, CSV export/copy, note creation, and property visibility/formulas/summaries

The runtime supports:

  • global filters and per-view filters
  • formula columns
  • base-level and per-view summaries
  • grouped result derivation from groupBy
  • sort order
  • row limits
  • search across displayed properties
  • frontmatter-backed note properties
  • toolbar create/copy/export workflows
  • a small set of file-derived columns such as file.name, file.path, file.folder, file.ext, file.ctime, file.mtime, file.size, and file.tags

Internal duplication

The plugin also carries a second, private Bases runtime model in packages/plugins/plugin-bases/src/bases-view/bases.svelte.ts.

Some of the duplication identified in the original audit has now been reduced:

  • DateValue and DurationValue helper behavior has been moved into the shared API
  • shared BasesViewConfig and shared option types are now used by the plugin’s view configuration surface

The remaining private model still owns the runtime-specific pieces, especially:

  • QueryController
  • the plugin-local BasesView base class
  • the running query/result lifecycle

Because the plugin and shared API evolve independently, parity work done in one layer does not automatically reach the other.

Confirmed parity gaps

1. Shared API type drift from upstream

The shared API does not currently match the upstream Bases types closely enough to claim parity.

Confirmed drift includes:

  • BasesPropertyId is declared as string | ... locally, while upstream requires a prefixed form ${BasesPropertyType}.${string}
  • BasesConfigFile now includes upstream-style formulas and summaries, and the runtime executes more of that config than it did initially, but not with full upstream semantics
  • BasesConfigFileFilter is currently a broad Record<string, unknown> instead of the recursive upstream union of string | { and } | { or } | { not }
  • BasesConfigFileView now includes groupBy and summaries, and the runtime uses both fields, but grouped-view and summary behavior is still narrower than upstream
  • option interfaces have been expanded to cover fields such as placeholder, filter, instant, and shouldHide, but view/config semantics are still incomplete in the runtime
  • BasesOptionGroup and BasesViewRegistration.options now match the upstream grouped-options and callback shape more closely
  • BasesView.createFileForView() is now implemented in the shared API and surfaced in the plugin toolbar, but the public API still does not drive the full runtime directly
  • BasesViewConfig.getEvaluatedFormula() is a stub that wraps raw config values instead of evaluating a formula in Bases context

2. Runtime config semantics are narrower than Obsidian Bases

The current plugin runtime only implements a subset of the documented Bases config model.

Confirmed gaps include:

  • the runtime now groups groupedData when groupBy is present in the view config, and the toolbar can edit that config; the list, cards, and table layouts now render grouped sections explicitly, but other grouped-view semantics are still narrower than upstream
  • no evidence that this context is supported the way the docs describe for opened, embedded, and sidebar Bases

The runtime query generator currently applies formulas, filters, sort, limit, grouped result derivation, and summary aggregation, but not the broader grouped-view workflows or this semantics that Obsidian exposes.

3. Built-in view coverage is incomplete

Obsidian help documents four built-in view layouts:

  • table
  • cards
  • list
  • map

The current plugin now registers:

  • table
  • cards
  • list
  • map
  • unknown

For map, the upstream product also depends on the Maps plugin. The local runtime now provides an explicit fallback state for that layout name, but it still does not implement a real map-capable renderer.

4. Toolbar and workflow coverage is incomplete

Obsidian’s help pages document a broader toolbar and workflow surface than the current plugin provides.

Currently present:

  • view selection and editing
  • limit
  • sort
  • filter
  • property visibility and formula editing
  • search over displayed properties through a toggleable panel above the active view
  • base-level and per-view summary editing for the selected property
  • group configuration editing

Documented but currently missing or not surfaced in the plugin:

  • no additional toolbar/workflow gaps are currently called out by this audit beyond the remaining layout/semantic differences below

5. File property coverage is incomplete

The docs describe a broader file-property surface than the plugin currently provides. Verified local coverage today is limited to:

  • file
  • file.file
  • file.name
  • file.basename
  • file.fullname
  • file.path
  • file.folder
  • file.ext
  • file.ctime
  • file.mtime
  • file.size
  • file.tags
  • file.links
  • file.embeds
  • file.backlinks
  • file.properties

The documented file-property names are now exposed in the runtime. Some related helpers such as hasLink() and hasProperty() exist, but richer file-value semantics are still missing.

6. Formula function coverage is partial

Verified local function coverage comes from two places:

  • explicit Bases registrations in plugin-bases/src/bases-view/functions.ts
  • generic PEaQL functions in node_modules/peaql/src/lib/query/query_env.ts

Verified function names available today:

  • local Bases-specific: isEmpty, isTruthy, toString, startsWith, endsWith, contains, containsAny, containsAll, containsAnyOf, containsAllOf, inFolder, hasLink, hasTag, hasProperty, length, map, filter, reduce, icon, title, relative, time, trim, split, replace, repeat, reverse, slice, ceil, floor, list, number, max, min, random, flat, join, sort, unique, escapeHTML, date, duration, file, html, image, link, if, isType, asFile, asLink, linksTo, keys, values, matches
  • PEaQL-provided names that overlap partially with Bases docs: abs, round, toFixed, today, now, format, lower

The remaining function parity gaps are now mostly about value-model depth and method-style semantics rather than missing registered function names.

Documented method-style functions that are missing, only partially present, or exposed under different names:

  • any: thin isTruthy, toString, and isType helpers are now registered, but the broader method-style value API is still incomplete
  • date: date(), format(), time(), relative() as date methods rather than only ad hoc helpers
  • string: method-style dispatch still needs broader runtime coverage beyond the newly registered names
  • number: method-style dispatch still needs broader runtime coverage beyond ceil and floor
  • list: method-style dispatch semantics still need deeper runtime coverage even though the helper names are now registered
  • link: asFile and linksTo are now registered as thin helpers, but richer link-value semantics are still missing
  • file: asLink is now registered as a thin helper, but richer file-value semantics are still missing
  • object: broader value-model integration still needed beyond keys and values
  • regexp: broader value-model integration still needed beyond matches

There is also naming drift from the docs:

  • docs use containsAny and containsAll
  • the runtime now exposes both the upstream names and the older local aliases containsAnyOf and containsAllOf

That reduces one compatibility gap, but formula compatibility is still not aligned with Obsidian’s public Bases language overall.

7. Value-model parity is incomplete

The shared API exports some upstream value classes by name, but behavior is incomplete.

Examples:

  • shared DateValue and DurationValue now expose the core helper methods that were called out in the original audit, but other value wrappers remain thin
  • shared LinkValue, HTMLValue, IconValue, ImageValue, TagValue, UrlValue, and RegExpValue are still mostly marker subclasses without upstream behavior
  • the private plugin model still owns runtime-specific view/controller behavior that is not shared through @lapis-notes/api

8. Parity reporting is more optimistic than the actual implementation

packages/api/scripts/generate-obsidian-parity.mjs marks many Bases symbols as implemented through explicit overrides. The generated parity fixture therefore reports name-level coverage, but it does not validate:

  • type accuracy
  • method completeness
  • runtime semantics
  • formula language compatibility
  • UI workflow parity

The current parity report is useful as an inventory, but not as proof of working Bases parity.

Plan to attain parity

1. Make @lapis-notes/api the single source of truth for Bases types and values

Unify the duplicated Bases model so the plugin consumes the same public implementation that the rest of the repo imports.

Work items:

  • move the richer private value and config types out of plugin-bases and into packages/api/src/lib/bases.ts
  • update plugin-bases to import those shared types instead of maintaining a parallel copy
  • align all exported types and method signatures to upstream obsidian.d.ts unless the repo intentionally documents a divergence

2. Close the shared API type gaps first

Before adding more UI behavior, the public API should match the upstream Bases contract closely.

Work items:

  • correct BasesPropertyId, BasesConfigFile, BasesConfigFileFilter, BasesConfigFileView, and option interfaces
  • finish aligning the remaining upstream fields and semantics such as stricter filter typing, placeholder, filter, instant, and shouldHide
  • update BasesViewRegistration.options to the upstream callback signature
  • implement BasesViewConfig.getEvaluatedFormula() rather than leaving it as a stub

3. Implement the documented Bases value model and function surface

Formula compatibility depends on both type wrappers and function dispatch.

Work items:

  • finish the value wrappers for DateValue, DurationValue, LinkValue, HTMLValue, ImageValue, IconValue, FileValue, ListValue, ObjectValue, and RegExpValue
  • add the documented global and method-style Bases functions under the upstream names
  • add compatibility aliases only where needed, but prefer Obsidian’s names over local variants
  • explicitly map or wrap PEaQL helpers so formula authors can use Obsidian syntax instead of query-engine internals
  • add tests for every documented function name and for key return-type/rendering behavior

4. Expand the query engine to match Bases semantics

The runtime currently handles formula projection, filtering, sorting, and limit. It needs the rest of the documented Bases execution model.

Work items:

  • deepen grouped-view rendering beyond the current list, cards, and table grouped sections
  • implement custom summary formulas beyond the current keyed aggregate support
  • add this binding semantics for opened, embedded, and sidebar Bases
  • support the documented file-property fields such as backlinks, links, embeds, and properties with clear refresh behavior

5. Bring the built-in views and toolbar up to documented coverage

Once the shared model and query runtime are aligned, the UI should match the documented Bases workflows.

Work items:

  • design a map view strategy that can either integrate with a future map capability or fail explicitly behind capability detection
  • extend grouped rendering and richer grouped workflows to cards, table, and any eventual map implementation

6. Replace name-level parity overrides with executable parity checks

The current parity fixture should become a starting point, not the success criterion.

Work items:

  • keep the symbol inventory, but stop treating explicit overrides as proof of parity
  • add shared API tests that validate signatures and observable behavior for Bases-specific classes
  • add plugin-level tests that exercise .base parsing, formulas, filters, summaries, grouping, and view registration
  • add function-coverage tests that compare the documented Bases function list against the registered/effective runtime function list

To reduce churn, parity work should land in this order:

  1. Continue unifying the shared API and plugin-private Bases model, starting with the remaining runtime classes instead of recreating more config/value shims locally.
  2. Align config and value types with upstream obsidian.d.ts.
  3. Add missing function names and value behaviors with exhaustive tests.
  4. Deepen method-style value behavior, grouped rendering, this, and missing file semantics.
  5. Finish the remaining built-in view behavior, especially real map capability and non-list grouped layouts.
  6. Add map view only after the runtime has a clear map-capability story.
  7. Tighten parity tooling so future audits measure behavior, not just symbol presence.

Current parity status

Current status can be summarized as follows:

  • shared API parity: partial and type-shim heavy
  • runtime query parity: partial but broader than the initial audit
  • documented function parity: partial but largely name-complete
  • built-in view parity: near-complete on layout names, incomplete on grouped behavior and map capability
  • toolbar/workflow parity: broad for the documented day-to-day controls
  • parity reporting fidelity: optimistic

The repository is far enough along to support basic Bases workflows, but it should not yet be considered at parity with upstream Obsidian Bases.