Move notifications to the declarative status bar #89

Closed
opened 2026-05-22 18:48:15 +00:00 by steve · 0 comments
Owner

Move the required notifications plugin off the imperative status-bar compatibility
host and onto the API-owned declarative status-bar registry.

Problem

The workspace status bar now renders API-owned declarative items from
app.statusBar, while Plugin.addStatusBarItem() remains mounted only as the
Obsidian-compatible raw DOM escape hatch. The required notifications plugin is
still using that compatibility path for its main status trigger, even though it
is first-party app chrome and should exercise the preferred declarative path.

That leaves one of the release-critical daily-use surfaces on the older
imperative host and weakens confidence that the declarative status-bar contract
can support dynamic first-party status UI such as background progress,
notification counts, context-key visibility, tooltips, and command dispatch.

Goal

Use the declarative status-bar registry for the notifications status trigger
while preserving the current user-facing behavior:

  • one compact aggregated background-progress status item
  • one notification bell/count status item when persisted notifications exist
  • stable inline progress text with full task details in the tooltip and
    notification center
  • click access to the notification center
  • compatibility for existing Plugin.addStatusBarItem() callers

Scope

  • Extend the API status-bar manager as needed for dynamic first-party updates.
  • Register notifications status-bar items through app.statusBar instead of
    Plugin.addStatusBarItem().
  • Use plugin-scoped context keys for notification status visibility.
  • Keep notification-center rendering owned by @lapis-notes/notifications.
  • Update focused API tests and workspace e2e coverage.
  • Update affected package and rendered spec pages when behavior or ownership
    changes.

Non-goals

  • Do not add arbitrary Svelte component rendering to the declarative status-bar
    descriptor unless the simpler command-driven design proves insufficient.
  • Do not remove Plugin.addStatusBarItem() or break Obsidian-compatible plugins.
  • Do not redesign notification persistence, native desktop mirroring, or the
    broader notification service.
  • Do not expand this into general declarative menus, tree views, badges, or
    output-channel work.

Acceptance Criteria

  • NotificationsPlugin no longer calls addStatusBarItem() for its main status
    trigger.
  • Notifications status-bar UI is registered through app.statusBar with stable
    item IDs, priority, alignment, tooltip, command, and when visibility.
  • Plugin-scoped context keys control visibility for at least active progress,
    unread notifications, and overall notification status availability.
  • The progress item keeps the existing compact behavior: short task title,
    percent when available, N more aggregation, and no volatile file paths in the
    inline label.
  • Full active-progress details remain available from the tooltip and the
    notification center.
  • Clicking the declarative status item opens the existing notification center
    surface or an equivalent notifications-owned center.
  • Bell/count behavior for unread persisted notifications remains intact.
  • Existing compatibility status-bar callers, including workspace-local or
    Obsidian-style plugins using Plugin.addStatusBarItem(), continue to render.
  • Workspace e2e coverage verifies compact progress text, tooltip detail,
    aggregation, and opening the notification center from the declarative item.
  • API tests cover status-bar dynamic updates and when-gated visibility.
  • Specs and package-local spec.md files document the new ownership and
    validation expectations.

Implementation Notes

  1. Add dynamic status-bar item updates in the API.

    Extend StatusBarManager with an update or upsert path so the notifications
    plugin can update text, icon, tooltip, command, and visibility without
    unregistering and re-registering items. Add focused Vitest coverage in
    packages/api/src/lib/__tests__/status-bar.test.ts.

  2. Use scoped context keys for visibility.

    In NotificationsPlugin.onload(), register plugin-scoped keys such as:

    • plugin.plugin-notifications.visible
    • plugin.plugin-notifications.hasProgress
    • plugin.plugin-notifications.hasUnread

    Register declarative status items with when clauses instead of mounting DOM
    into the status bar.

  3. Split notification status state from the Svelte trigger.

    Extract the current label, tooltip, task derivation, and unread-count logic
    from notification-status-item.svelte into a small helper or model module so
    the plugin runtime and notification center UI share the same formatting
    behavior.

  4. Replace addStatusBarItem() usage.

    Remove mountStatusBar() and destroyStatusBar() from
    notifications-plugin.ts. Register declarative items instead:

    • progress item: spinner icon, compact task label, tooltip, command, and
      when: plugin.plugin-notifications.hasProgress
    • bell item: bell icon, unread count, tooltip, command, and
      when: !plugin.plugin-notifications.hasProgress && plugin.plugin-notifications.visible
  5. Preserve the notification center.

    Because the current declarative status bar does not support arbitrary Svelte
    popover content, register a command such as
    plugin-notifications:toggle-center. The declarative item invokes that
    command. The plugin can render a separate notification-center overlay or
    panel owned by the notifications package and positioned above the status bar
    without using the compatibility status-bar DOM host.

  6. Update workspace renderer support if needed.

    Add a stable diagnostic attribute such as
    data-status-bar-item-id={item.id} to declarative status buttons so tests can
    target declarative items without relying on text or DOM order.

  7. Update tests.

    • API: status-bar update or upsert behavior, plus when visibility with
      context keys.
    • Workspace e2e: update notifications-status-bar.spec.ts to target
      declarative status items and verify the same compact label, tooltip,
      aggregation, and center-opening behavior.
    • Compatibility: confirm the raw DOM host still works for existing
      imperative callers.
  8. Update specs.

    Touch at least:

    • spec/src/20-packages/plugins/notifications/index.md
    • packages/plugins/plugin-notifications/spec.md
    • spec/src/20-packages/workspace/index.md
    • packages/workspace/spec.md
    • packages/api/spec.md if the status-bar manager API changes

Suggested Files or Specs To Inspect

  • packages/api/src/lib/status-bar.svelte.ts
  • packages/api/src/lib/plugin.ts
  • packages/api/src/lib/__tests__/status-bar.test.ts
  • packages/workspace/src/lib/components/status-bar/status-bar.svelte
  • packages/workspace/e2e/notifications-status-bar.spec.ts
  • packages/plugins/plugin-notifications/src/notifications-plugin.ts
  • packages/plugins/plugin-notifications/src/notification-status-item.svelte
  • packages/plugins/plugin-notifications/spec.md
  • spec/src/20-packages/plugins/notifications/index.md
  • spec/src/20-packages/workspace/index.md
  • spec/src/30-cross-package-contracts/plugin-system/design.md
  • spec/src/50-roadmap/complete-daily-use-app.md

Validation Commands

pnpm --filter @lapis-notes/api check
pnpm --filter @lapis-notes/notifications check
pnpm --filter @lapis-notes/workspace check
make spec-lint
mdbook build spec
pnpm test:smoke

Follow-up Tasks

  • Consider richer declarative status-bar rendering only if multiple future
    first-party or manifest-only contributors need component-hosted UI.
  • Consider documenting a stable notification-center command contract for other
    first-party surfaces after this migration lands.

Implementation Summary

Moved notifications off Plugin.addStatusBarItem onto app.statusBar with API upsert support, plugin-scoped visibility context keys, a command-opened notification center panel, declarative status-bar e2e coverage, and synced API/workspace/notifications specs.

Move the required notifications plugin off the imperative status-bar compatibility host and onto the API-owned declarative status-bar registry. ## Problem The workspace status bar now renders API-owned declarative items from `app.statusBar`, while `Plugin.addStatusBarItem()` remains mounted only as the Obsidian-compatible raw DOM escape hatch. The required notifications plugin is still using that compatibility path for its main status trigger, even though it is first-party app chrome and should exercise the preferred declarative path. That leaves one of the release-critical daily-use surfaces on the older imperative host and weakens confidence that the declarative status-bar contract can support dynamic first-party status UI such as background progress, notification counts, context-key visibility, tooltips, and command dispatch. ## Goal Use the declarative status-bar registry for the notifications status trigger while preserving the current user-facing behavior: - one compact aggregated background-progress status item - one notification bell/count status item when persisted notifications exist - stable inline progress text with full task details in the tooltip and notification center - click access to the notification center - compatibility for existing `Plugin.addStatusBarItem()` callers ## Scope - Extend the API status-bar manager as needed for dynamic first-party updates. - Register notifications status-bar items through `app.statusBar` instead of `Plugin.addStatusBarItem()`. - Use plugin-scoped context keys for notification status visibility. - Keep notification-center rendering owned by `@lapis-notes/notifications`. - Update focused API tests and workspace e2e coverage. - Update affected package and rendered spec pages when behavior or ownership changes. ## Non-goals - Do not add arbitrary Svelte component rendering to the declarative status-bar descriptor unless the simpler command-driven design proves insufficient. - Do not remove `Plugin.addStatusBarItem()` or break Obsidian-compatible plugins. - Do not redesign notification persistence, native desktop mirroring, or the broader notification service. - Do not expand this into general declarative menus, tree views, badges, or output-channel work. ## Acceptance Criteria - `NotificationsPlugin` no longer calls `addStatusBarItem()` for its main status trigger. - Notifications status-bar UI is registered through `app.statusBar` with stable item IDs, priority, alignment, tooltip, command, and `when` visibility. - Plugin-scoped context keys control visibility for at least active progress, unread notifications, and overall notification status availability. - The progress item keeps the existing compact behavior: short task title, percent when available, `N more` aggregation, and no volatile file paths in the inline label. - Full active-progress details remain available from the tooltip and the notification center. - Clicking the declarative status item opens the existing notification center surface or an equivalent notifications-owned center. - Bell/count behavior for unread persisted notifications remains intact. - Existing compatibility status-bar callers, including workspace-local or Obsidian-style plugins using `Plugin.addStatusBarItem()`, continue to render. - Workspace e2e coverage verifies compact progress text, tooltip detail, aggregation, and opening the notification center from the declarative item. - API tests cover status-bar dynamic updates and `when`-gated visibility. - Specs and package-local `spec.md` files document the new ownership and validation expectations. ## Implementation Notes 1. Add dynamic status-bar item updates in the API. Extend `StatusBarManager` with an update or upsert path so the notifications plugin can update text, icon, tooltip, command, and visibility without unregistering and re-registering items. Add focused Vitest coverage in `packages/api/src/lib/__tests__/status-bar.test.ts`. 2. Use scoped context keys for visibility. In `NotificationsPlugin.onload()`, register plugin-scoped keys such as: - `plugin.plugin-notifications.visible` - `plugin.plugin-notifications.hasProgress` - `plugin.plugin-notifications.hasUnread` Register declarative status items with `when` clauses instead of mounting DOM into the status bar. 3. Split notification status state from the Svelte trigger. Extract the current label, tooltip, task derivation, and unread-count logic from `notification-status-item.svelte` into a small helper or model module so the plugin runtime and notification center UI share the same formatting behavior. 4. Replace `addStatusBarItem()` usage. Remove `mountStatusBar()` and `destroyStatusBar()` from `notifications-plugin.ts`. Register declarative items instead: - progress item: spinner icon, compact task label, tooltip, command, and `when: plugin.plugin-notifications.hasProgress` - bell item: bell icon, unread count, tooltip, command, and `when: !plugin.plugin-notifications.hasProgress && plugin.plugin-notifications.visible` 5. Preserve the notification center. Because the current declarative status bar does not support arbitrary Svelte popover content, register a command such as `plugin-notifications:toggle-center`. The declarative item invokes that command. The plugin can render a separate notification-center overlay or panel owned by the notifications package and positioned above the status bar without using the compatibility status-bar DOM host. 6. Update workspace renderer support if needed. Add a stable diagnostic attribute such as `data-status-bar-item-id={item.id}` to declarative status buttons so tests can target declarative items without relying on text or DOM order. 7. Update tests. - API: status-bar update or upsert behavior, plus `when` visibility with context keys. - Workspace e2e: update `notifications-status-bar.spec.ts` to target declarative status items and verify the same compact label, tooltip, aggregation, and center-opening behavior. - Compatibility: confirm the raw DOM host still works for existing imperative callers. 8. Update specs. Touch at least: - `spec/src/20-packages/plugins/notifications/index.md` - `packages/plugins/plugin-notifications/spec.md` - `spec/src/20-packages/workspace/index.md` - `packages/workspace/spec.md` - `packages/api/spec.md` if the status-bar manager API changes ## Suggested Files or Specs To Inspect - `packages/api/src/lib/status-bar.svelte.ts` - `packages/api/src/lib/plugin.ts` - `packages/api/src/lib/__tests__/status-bar.test.ts` - `packages/workspace/src/lib/components/status-bar/status-bar.svelte` - `packages/workspace/e2e/notifications-status-bar.spec.ts` - `packages/plugins/plugin-notifications/src/notifications-plugin.ts` - `packages/plugins/plugin-notifications/src/notification-status-item.svelte` - `packages/plugins/plugin-notifications/spec.md` - `spec/src/20-packages/plugins/notifications/index.md` - `spec/src/20-packages/workspace/index.md` - `spec/src/30-cross-package-contracts/plugin-system/design.md` - `spec/src/50-roadmap/complete-daily-use-app.md` ## Validation Commands ```bash pnpm --filter @lapis-notes/api check pnpm --filter @lapis-notes/notifications check pnpm --filter @lapis-notes/workspace check make spec-lint mdbook build spec pnpm test:smoke ``` ## Follow-up Tasks - Consider richer declarative status-bar rendering only if multiple future first-party or manifest-only contributors need component-hosted UI. - Consider documenting a stable notification-center command contract for other first-party surfaces after this migration lands. ## Implementation Summary Moved notifications off Plugin.addStatusBarItem onto app.statusBar with API upsert support, plugin-scoped visibility context keys, a command-opened notification center panel, declarative status-bar e2e coverage, and synced API/workspace/notifications specs. <!-- backlog:task_id=ROADMAP-007 source_spec=spec/src/50-roadmap/complete-daily-use-app.md -->
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lapis-notes/lapis#89
No description provided.