Skip to content

feat(plugins): per-session activation + config UI (dashboard) - #445

Merged
rmyndharis merged 5 commits into
mainfrom
feat/plugin-per-session-dashboard
Jun 23, 2026
Merged

feat(plugins): per-session activation + config UI (dashboard)#445
rmyndharis merged 5 commits into
mainfrom
feat/plugin-per-session-dashboard

Conversation

@rmyndharis

Copy link
Copy Markdown
Owner

What

Completes the v0.7 per-session story in the dashboard — surfaces the two backends that shipped without UI (#438 activation, #441 per-session config) in a tabbed plugin config modal.

  • API client: Plugin gains sessionScoped/activeSessions/sessionConfig; setSessions + updateSessionConfig.
  • Config modal → tabs for session-scoped, non-engine plugins: Configuration (unchanged) + Sessions:
    • Activation: All sessions vs a chosen set → PUT /plugins/:id/sessions.
    • Per-session config override (when the plugin has a configSchema/configUi): pick a session → edit via the B-lite form (seeded from the resolved slice) or the sandboxed iframe (sessionId to the bridge). Clear override resets to Global.
  • i18n: 13 new keys × 9 locales (parity gate passes). CSS for tabs + Sessions sections.

Dashboard + one backend correctness fix; design spec in docs/superpowers/specs/.

Review (adversarial, 2 dimensions → verified)

Found 7 issues — all fixed:

  • HIGH: resolvePluginConfig now deep-merges (was shallow), so a sparse override can't drop an untouched nested base key (e.g. a nested secret) at runtime; arrays/scalars still replace (+unit tests).
  • Per-session save centralized in sparseSessionOverride(): only non-secret keys differing from base + all secret keys; no spurious empty override for untouched optional fields; used by both the B-lite form and the iframe path.
  • Config modal derives the open plugin from the live query (no stale Sessions-tab state after save + invalidate).
  • Per-session form renders the localized schema, matching the Configuration tab.
  • Noted as intentional: "Specific sessions" + none selected = deactivate everywhere (backend [] = none).

Testing

Backend tsc + 1231 jest (deep-merge resolver tests + per-session + worker integration); dashboard build (tsc) + lint + i18n parity (556 keys × 9). No dashboard jest (project convention).

Sandboxed-iframe per-session is included; the secret round-trip stays server-side (restoreSecretConfig).

Surfaces the two per-session backends that shipped without UI (#438 activation,
#441 per-session config) in a tabbed plugin config modal.

- API client: Plugin type gains sessionScoped/activeSessions/sessionConfig;
  setSessions (PUT /plugins/:id/sessions) + updateSessionConfig
  (PUT /plugins/:id/config/:sessionId).
- Config modal: session-scoped non-engine plugins get [Configuration] [Sessions]
  tabs. Configuration is unchanged. Sessions has:
  - Activation: All sessions vs a chosen set -> setSessions.
  - Per-session config override (when the plugin has a configSchema/configUi):
    a session picker -> the B-lite form (seeded from the resolved slice) or the
    sandboxed iframe (sessionId passed to the bridge). Save writes only non-secret
    keys that differ from base + all secret keys (sparse override: untouched keys
    inherit Global; the backend restores untouched secrets), so editing Global
    still propagates to a session's unchanged fields. Clear override resets to
    Global.
- i18n: 13 new keys across all 9 locales (parity gate passes). CSS for tabs +
  Sessions sections.

Dashboard-only; all backend APIs pre-existed. Build (tsc) + lint + i18n parity green.
… live modal state)

Adversarial review of the per-session dashboard UI found 7 issues; fixes:

- HIGH: resolvePluginConfig now DEEP-merges the override over the base (was
  shallow). A sparse override (e.g. an object whose nested secret the operator
  didn't touch) no longer drops the base's untouched nested keys at runtime;
  arrays/scalars still replace. (+unit tests for nested merge + array replace.)
- The per-session override save is centralized in sparseSessionOverride(): only
  non-secret keys that differ from base, plus all secret keys; an untouched
  optional field with no Global value no longer creates a spurious empty
  override. Used by BOTH the B-lite form and the iframe per-session save (the
  iframe path was writing every resolved key as an override).
- The config modal derives the open plugin from the LIVE plugins query instead
  of an open-time snapshot, so the Sessions tab reflects fresh
  activeSessions/sessionConfig after a save + invalidate.
- The per-session form renders the localized schema (labels/descriptions),
  matching the Configuration tab.

Backend tsc + 1231 jest; dashboard build + lint + i18n parity green.
…d refetch

configPlugin is derived from the live query, so the plugin prop gets a new
reference on every refetch (refetchOnWindowFocus). Key the override-seed effect
on [selSession, plugin.id] instead of the plugin object so it reseeds only when
the selected session/plugin changes — a window-focus refetch mid-edit no longer
clobbers the operator's unsaved override.
…f-rows secret caveat)

Re-review noted the docstring overstated the 'untouched secret inherits' promise:
it holds for top-level + object-nested secrets, but NOT a secret column inside an
array-of-rows (arrays replace wholesale on resolve; the redacted dashboard can't
resend untouched rows' real secrets). Documented the limitation; no bundled
plugin ships that shape. Comment-only.
@rmyndharis
rmyndharis merged commit e37e33f into main Jun 23, 2026
5 checks passed
@rmyndharis
rmyndharis deleted the feat/plugin-per-session-dashboard branch June 23, 2026 07:52
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