Skip to content

Plugin: properties UI + Bases-style vault tables (#72)#143

Merged
thetechjon merged 1 commit into
devfrom
feat/properties-tables-plugin-72
Jun 7, 2026
Merged

Plugin: properties UI + Bases-style vault tables (#72)#143
thetechjon merged 1 commit into
devfrom
feat/properties-tables-plugin-72

Conversation

@thetechjon
Copy link
Copy Markdown
Collaborator

Summary

Closes #72 by building a noteser-properties reference plugin under
public/plugins/. Two surfaces, both via the Plugin API v1.2 stack
that landed across PRs A–F + the post-v1.2 VNode event delivery
follow-up (#142):

  • Sidebar Properties+ panel — renders the active note's
    frontmatter as a key/value list. One input VNode per property;
    "+ Add property" opens a key + value draft row. Every commit
    calls ctx.vault.write.updateNote(id, { frontmatter }) so the
    host's existing writeFrontmatter helper re-serialises the block
    and the note body stays untouched. Subscribes to
    ctx.vault.events.onActiveNoteChange + onNoteSaved so the
    panel re-pulls the host-parsed frontmatter on every relevant
    change.
  • Fullscreen Vault tables view — Obsidian Bases-style table
    over the whole vault. ctx.vault.read.getAllNotes() (falls back
    to ctx.vault.read.stream() when the vault is too large for the
    single-envelope path), columns inferred as the union of every
    note's frontmatter keys. Filter input matches across title,
    folder path, every stringified value. Column headers are button
    VNodes — click to sort, click again to flip direction. Each row
    renders the title as a link VNode so the PR Wire VNode event delivery + wikilink intercept (post-v1.2 followup) #142 wikilink
    intercept opens the note. "Save current view as note" calls
    ctx.vault.write.createNote with the rendered markdown table.

Coexists with the existing core PropertiesPanel — both stay
selectable per Jon's rule.

Why a plugin (not core)

The plugin platform was specifically scoped to unlock these four
issues (#70, #71, #72, #73) as plugins rather than core code; the
v1.2 plan doc calls #72 out by name in §13. Shipping as a plugin
keeps:

  • Bases parity on the surface that Obsidian users already know
  • The vault.write audit trail engaged for every property edit
  • The pluggable boundary clean — future schemas (Templater, Tasks
    cross-references) can replace this without touching core

Frontmatter inference notes

  • Source: NoteWithBody.frontmatter from the host. The plugin
    never re-parses YAML; js-yaml is intentionally NOT a
    dependency (the plan's "already a dep — check package.json"
    hint checked — it isn't; the host's lightweight parser already
    hands us a typed object).
  • Per-column type: string / number / date (ISO date pattern)
    / tag-array / boolean. Heterogeneous columns reduce to
    string so sort stays sane on a mixed vault.
  • Numeric-shaped strings (e.g. frontmatter priority: 3 written
    bare) classify as number so a "priority" column sorts
    numerically even though the underlying values arrive as JS
    strings from some other plugin's writes.
  • Columns whose every value was empty are dropped — they would
    otherwise add noise to vaults with one-off bare key: lines.
  • tags always renders first; remaining columns alphabetical.

Filter / sort UX

  • Filter is a single search input. Case-insensitive substring
    match across title, folder path, and every stringified
    frontmatter value (arrays joined with , , objects JSON-ified).
    Empty / whitespace filter keeps every note.
  • Click a header to sort by that column. Clicking the same header
    again flips ascending↔descending; clicking a different header
    resets to ascending. The active sort is shown in the header
    label with a / glyph.
  • Empties (null / undefined / empty string / missing key) always
    sort last in ascending direction, reversed by desc.
  • Title and folder-path get their own special sort keys (_title,
    _folder) so they remain sortable even on vaults whose
    frontmatter never declares those keys.

Test plan

  • npm run lint clean
  • npx tsc --noEmit zero errors
  • npm test -- --ci green (2608 passing, 17 pre-existing
    skips — the new suite adds 44 tests covering inference,
    filter, sort, value coercion, and markdown export)
  • Manual smoke: install plugin via Settings → Plugins, open a
    note with frontmatter, confirm Properties+ panel renders +
    lets you edit. Run "Open Vault tables" from the palette,
    confirm table appears, sort by a column, click a row to
    open that note.

🤖 Generated with Claude Code

Adds `noteser-properties` under public/plugins/ — closes issue #72 by
implementing both halves of the Obsidian Bases-parity ask as a plugin
rather than core code. PR #142 just landed the VNode event delivery
that makes interactive plugin UIs work end-to-end; this is the first
feature plugin that exercises the full v1.2 stack.

Surfaces:

- Sidebar `Properties+` panel. Renders the active note's frontmatter
  as a key/value list with one `input` VNode per property. "+ Add
  property" opens a key + value draft row. Each commit calls
  `ctx.vault.write.updateNote(id, { frontmatter })`. Subscribes to
  `ctx.vault.events.onActiveNoteChange` and `onNoteSaved` so the
  panel re-pulls the host-parsed frontmatter on every relevant
  change.

- Fullscreen `Vault tables` view. `ctx.vault.read.getAllNotes()`
  (falls back to `stream()` for vault-too-large), columns inferred
  as the union of every note's frontmatter keys, every row rendered
  as a `box` with a `link` VNode for the title (clicking dispatches
  the PR #142 wikilink intercept). Top input filters across title,
  folder path, every stringified frontmatter value. Column headers
  are `button` VNodes — click to sort, click again to flip
  direction. "Save current view as note" calls
  `ctx.vault.write.createNote` with the rendered markdown table.

Frontmatter handling:

- Source: `NoteWithBody.frontmatter` straight from the host's
  parser. The plugin never re-parses YAML; js-yaml is not a
  dependency (the plan's check confirmed it's not in package.json).
- Type inference per column: `string` / `number` / `date` (ISO
  date) / `tag-array` / `boolean`. Heterogeneous columns fall back
  to `string` so sort stays sane.

Test plan:
- 44 unit tests cover inference (string/number/date/tag-array/
  boolean/empty/heterogeneous), filter (title, folder, tags, status,
  numbers, case-insensitivity, empty filter), sort (every column
  type, asc + desc, empties last, _title / _folder special keys,
  immutability), value coercion (back to numbers / arrays / booleans
  on edit), and the markdown table serialiser (header + separator,
  pipe escaping). Mirror lives at
  src/plugins/propertiesPluginLogic.ts; production logic at
  public/plugins/noteser-properties/main.js (must move in lock
  step).

Coexistence: the existing core `PropertiesPanel` is unchanged — the
plugin adds a second, richer surface alongside it.

`npm run lint`, `npx tsc --noEmit`, `npm test -- --ci` all green.

Closes #72.
@thetechjon thetechjon merged commit 482a832 into dev Jun 7, 2026
3 checks passed
@thetechjon thetechjon deleted the feat/properties-tables-plugin-72 branch June 7, 2026 06:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant