feat: add app settings, vault plugin state, and first-run vault selection

- internal/core/appsettings/ — app settings manager (~/.config/verstak/config.json)
- internal/core/pluginstate/ — vault plugin state (.verstak/plugins.json)
- internal/api/app.go — Wails bindings for app settings + plugin state
- main.go — init app settings, auto-open vault, init plugin state, disabled plugin filtering
- Plugin state: enable/disable, desired plugins, missing-installed tracking
- App settings: currentVaultPath, recentVaults, theme, devMode, windowState
This commit is contained in:
2026-06-17 03:37:15 +08:00
parent c0ea1972f6
commit 04dbfa056e
6 changed files with 992 additions and 1 deletions
+97
View File
@@ -8,11 +8,13 @@ import (
"path/filepath"
"strings"
"github.com/verstak/verstak-desktop/internal/core/appsettings"
"github.com/verstak/verstak-desktop/internal/core/capability"
"github.com/verstak/verstak-desktop/internal/core/contribution"
"github.com/verstak/verstak-desktop/internal/core/events"
"github.com/verstak/verstak-desktop/internal/core/permissions"
"github.com/verstak/verstak-desktop/internal/core/plugin"
"github.com/verstak/verstak-desktop/internal/core/pluginstate"
"github.com/verstak/verstak-desktop/internal/core/storage"
"github.com/verstak/verstak-desktop/internal/core/vault"
)
@@ -26,6 +28,8 @@ type App struct {
plugins []plugin.Plugin
vault *vault.Vault
storage *storage.Storage
appSettings *appsettings.Manager
pluginState *pluginstate.Manager
}
// NewApp creates a new App instance.
@@ -37,6 +41,8 @@ func NewApp(
plugins []plugin.Plugin,
vaultService *vault.Vault,
storageService *storage.Storage,
appSettingsMgr *appsettings.Manager,
pluginStateMgr *pluginstate.Manager,
) *App {
return &App{
capRegistry: capReg,
@@ -46,6 +52,8 @@ func NewApp(
plugins: plugins,
vault: vaultService,
storage: storageService,
appSettings: appSettingsMgr,
pluginState: pluginStateMgr,
}
}
@@ -149,6 +157,14 @@ func (a *App) ReloadPlugins() (int, string) {
for i := range plugins {
p := &plugins[i]
// Skip disabled plugins
if a.pluginState != nil && a.pluginState.IsDisabled(p.Manifest.ID) {
log.Printf("[plugin] %s: disabled in vault plugin state — skipping", p.Manifest.ID)
p.Status = plugin.StatusDisabled
p.Enabled = false
continue
}
if len(p.Manifest.Provides) > 0 {
if err := a.capRegistry.Register(p.Manifest.ID, p.Manifest.Provides); err != nil {
log.Printf("[plugin] %s: capability registration failed: %v", p.Manifest.ID, err)
@@ -329,6 +345,87 @@ func (a *App) WritePluginDataJSON(pluginID, name string, data map[string]interfa
return ""
}
// ─── App Settings API ──────────────────────────────────────
// GetAppSettings returns the current app settings.
func (a *App) GetAppSettings() map[string]interface{} {
if a.appSettings == nil {
return map[string]interface{}{"status": "not initialized"}
}
cfg := a.appSettings.Get()
return map[string]interface{}{
"schemaVersion": cfg.SchemaVersion,
"currentVaultPath": cfg.CurrentVaultPath,
"recentVaults": cfg.RecentVaults,
"theme": cfg.Theme,
"devMode": cfg.DevMode,
"userPluginsDir": cfg.UserPluginsDir,
"lastOpenedAt": cfg.LastOpenedAt,
}
}
// UpdateAppSettings patches and saves app settings.
func (a *App) UpdateAppSettings(patch map[string]interface{}) string {
if a.appSettings == nil {
return "app settings not initialized"
}
cfg := &appsettings.Config{}
if v, ok := patch["theme"].(string); ok && v != "" {
cfg.Theme = v
}
if v, ok := patch["devMode"].(bool); ok {
cfg.DevMode = v
}
if v, ok := patch["userPluginsDir"].(string); ok && v != "" {
cfg.UserPluginsDir = v
}
if err := a.appSettings.Update(cfg); err != nil {
return err.Error()
}
return ""
}
// ─── Vault Plugin State API ────────────────────────────────
// GetVaultPluginState returns the current vault plugin state.
func (a *App) GetVaultPluginState() map[string]interface{} {
if a.pluginState == nil {
return map[string]interface{}{"status": "not initialized"}
}
state := a.pluginState.Get()
return map[string]interface{}{
"schemaVersion": state.SchemaVersion,
"enabledPlugins": state.EnabledPlugins,
"disabledPlugins": state.DisabledPlugins,
"desiredPlugins": state.DesiredPlugins,
"updatedAt": state.UpdatedAt,
}
}
// EnablePlugin enables a plugin in the vault.
func (a *App) EnablePlugin(pluginID string) string {
if a.pluginState == nil {
return "plugin state not initialized"
}
if err := a.pluginState.EnablePlugin(pluginID); err != nil {
return err.Error()
}
return ""
}
// DisablePlugin disables a plugin in the vault.
func (a *App) DisablePlugin(pluginID string) string {
if a.pluginState == nil {
return "plugin state not initialized"
}
if err := a.pluginState.DisablePlugin(pluginID); err != nil {
return err.Error()
}
return ""
}
// ContributionSummary aggregates all contribution types for the frontend.
type ContributionSummary struct {
Views []contribution.ContributionView `json:"views"`