feat(m4b): add vault selection UI, enable/disable toggle, missing-installed UI

- Add VaultSelection.svelte: first-run vault create/open/recent UI
- Update App.svelte: vault check on startup, show VaultSelection when needed
- Update PluginCard.svelte: enable/disable buttons, vault state awareness
- Update PluginManager.svelte: enable/disable handlers, missing-installed section
- Add SetCurrentVault Wails API binding
- Add RecordDesiredPlugin Wails API binding
- Record desired plugins on discovery (only when vault open)
- Fix addRecent: remove duplicate sort, clean up unused import
- Update smoke-platform.sh: enable/disable lifecycle test
- Add runEnableDisableTest: vault create/open, disable/enable, plugins.json verify
This commit is contained in:
2026-06-17 04:19:13 +08:00
parent c8d2560bb2
commit a6f9e85f13
9 changed files with 829 additions and 77 deletions
+51
View File
@@ -191,6 +191,17 @@ func (a *App) ReloadPlugins() (int, string) {
if p.Manifest.Contributes != nil {
a.contribRegistry.Register(p.Manifest.ID, p.Manifest.Contributes)
}
// Record as desired plugin in vault state (only if vault is open)
if a.pluginState != nil && a.vault != nil && a.vault.GetVaultStatus() == vault.StatusOpen {
source := p.Manifest.Source
if source == "" {
source = "unknown"
}
if err := a.pluginState.RecordDesiredPlugin(p.Manifest.ID, p.Manifest.Version, source); err != nil {
log.Printf("[plugin] %s: failed to record desired: %v", p.Manifest.ID, err)
}
}
}
a.plugins = plugins
@@ -387,6 +398,35 @@ func (a *App) UpdateAppSettings(patch map[string]interface{}) string {
return ""
}
// SetCurrentVault sets the current vault path in app settings and re-opens the vault.
func (a *App) SetCurrentVault(path string) string {
if a.appSettings == nil {
return "app settings not initialized"
}
if a.vault == nil {
return "vault service not initialized"
}
// Try to open the vault first
if err := a.vault.OpenVault(path); err != nil {
return fmt.Sprintf("failed to open vault: %v", err)
}
// Save to app settings
if err := a.appSettings.SetCurrentVault(path); err != nil {
return fmt.Sprintf("failed to save app settings: %v", err)
}
// Load plugin state for the vault
if a.pluginState != nil {
if err := a.pluginState.Load(); err != nil {
log.Printf("[api] SetCurrentVault: warning loading plugin state: %v", err)
}
}
// Register vault capability
if err := a.capRegistry.Register("verstak-desktop", []string{"verstak/core/vault/v1"}); err != nil {
log.Printf("[api] SetCurrentVault: failed to register vault capability: %v", err)
}
return ""
}
// ─── Vault Plugin State API ────────────────────────────────
// GetVaultPluginState returns the current vault plugin state.
@@ -426,6 +466,17 @@ func (a *App) DisablePlugin(pluginID string) string {
return ""
}
// RecordDesiredPlugin records a plugin as desired for this vault.
func (a *App) RecordDesiredPlugin(pluginID, version, source string) string {
if a.pluginState == nil {
return "plugin state not initialized"
}
if err := a.pluginState.RecordDesiredPlugin(pluginID, version, source); err != nil {
return err.Error()
}
return ""
}
// ContributionSummary aggregates all contribution types for the frontend.
type ContributionSummary struct {
Views []contribution.ContributionView `json:"views"`