mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-07-04 03:45:20 +00:00
Document setting definition repository design
This commit is contained in:
@@ -0,0 +1,349 @@
|
||||
# Architectural Decision Record: Setting Definition Repository
|
||||
|
||||
## Status
|
||||
|
||||
Proposed
|
||||
|
||||
## Release
|
||||
|
||||
Not scheduled. Intended as a design direction before refactoring the settings dialogue.
|
||||
|
||||
## Context
|
||||
|
||||
The current settings dialogue is implemented around `ObsidianLiveSyncSettingTab`.
|
||||
It is effective and feature rich, but several responsibilities are combined in the
|
||||
same layer:
|
||||
|
||||
- editing-state buffering and dirty-state tracking,
|
||||
- local-only dialogue state such as `configPassphrase`, `preset`, `syncMode`, and
|
||||
`deviceAndVaultName`,
|
||||
- persistence through `SettingService`,
|
||||
- Obsidian `Setting` component rendering,
|
||||
- pane layout and visibility rules,
|
||||
- validation and value coercion,
|
||||
- setup wizard transitions,
|
||||
- rebuild/restart side effects,
|
||||
- remote diagnostics and maintenance actions.
|
||||
|
||||
Some setting metadata already exists in `configurationNames` and related setting
|
||||
constants. This is a useful seed, but it is not a complete source of truth. The
|
||||
metadata does not currently describe storage domain, value kind, validation,
|
||||
capability requirements, migration behaviour, cross-setting dependencies, or
|
||||
whether a value should be rendered by a generic control or a custom pane.
|
||||
|
||||
The settings dialogue also contains a mix of simple controls and workflow panels.
|
||||
Examples:
|
||||
|
||||
- Simple controls: `showStatusOnEditor`, `syncOnSave`, `customChunkSize`,
|
||||
`readChunksOnline`, `useTimeouts`.
|
||||
- Derived or dialogue-only values: `syncMode`, `preset`, `configPassphrase`,
|
||||
`deviceAndVaultName`.
|
||||
- Workflow panels: remote configuration management, E2EE setup, setup wizard,
|
||||
local/remote rebuild, maintenance commands, Customisation Sync dialogue open.
|
||||
|
||||
This matters primarily for maintainability and platform independence. A setting
|
||||
should have one shared definition of its domain meaning regardless of whether it
|
||||
is displayed in Obsidian, surfaced in a CLI, used by a WebApp, or documented.
|
||||
Tests benefit from this separation, but testability is a consequence rather than
|
||||
the main design goal.
|
||||
|
||||
Real Obsidian E2E remains the right signal for Obsidian shell behaviour such as
|
||||
opening the settings tab, rendering real Obsidian components, and verifying
|
||||
user-visible workflows. Harness-based tests can then focus on deterministic
|
||||
setting semantics that no longer depend on mounting the whole Obsidian setting
|
||||
tab.
|
||||
|
||||
## Decision
|
||||
|
||||
Introduce a platform-neutral Setting Definition Repository as the source of
|
||||
truth for setting metadata and setting semantics.
|
||||
|
||||
The repository should live in the shared domain layer, not under the Obsidian
|
||||
dialogue implementation and not under a generic model bucket. In the current
|
||||
layout this means `src/lib/src/common/settings`.
|
||||
|
||||
The repository should not be an Obsidian UI abstraction. It should describe what
|
||||
a setting is and how it behaves. Obsidian, CLI, WebApp, tests, and documentation
|
||||
can then consume the same definitions through their own renderers or adapters.
|
||||
The Obsidian settings tab should use an Obsidian renderer that maps repository
|
||||
definitions to native Obsidian `Setting` components for simple controls, while
|
||||
workflow panes remain custom.
|
||||
|
||||
The existing Obsidian setting dialogue should be migrated incrementally. Complex
|
||||
workflow panes should remain custom-rendered at first. Simple controls should
|
||||
move to repository-driven rendering first.
|
||||
|
||||
## Proposed Model
|
||||
|
||||
Each setting definition should describe a single setting key or a dialogue-only
|
||||
virtual key.
|
||||
|
||||
```ts
|
||||
type SettingStorageDomain = "persisted" | "local" | "derived" | "ephemeral";
|
||||
|
||||
type SettingValueKind = "boolean" | "text" | "password" | "number" | "select" | "textarea" | "string-list" | "custom";
|
||||
|
||||
type SettingCapability = "database-user" | "server-admin" | "filesystem" | "obsidian-shell" | "obsidian-plugin-host";
|
||||
|
||||
type SettingDefinition<TSettings, TKey extends keyof TSettings | string> = {
|
||||
key: TKey;
|
||||
storage: SettingStorageDomain;
|
||||
kind: SettingValueKind;
|
||||
defaultValue?: unknown;
|
||||
labelKey: string;
|
||||
descriptionKey?: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
category: string;
|
||||
pane?: string;
|
||||
section?: string;
|
||||
level?: "ADVANCED" | "POWER_USER" | "EDGE_CASE";
|
||||
status?: "BETA" | "ALPHA" | "EXPERIMENTAL";
|
||||
obsolete?: boolean;
|
||||
internal?: boolean;
|
||||
placeholder?: string;
|
||||
options?: Record<string, string>;
|
||||
secret?: boolean;
|
||||
requiredCapabilities?: SettingCapability[];
|
||||
visible?: (context: SettingEvaluationContext<TSettings>) => boolean;
|
||||
enabled?: (context: SettingEvaluationContext<TSettings>) => boolean;
|
||||
validate?: (value: unknown, context: SettingEvaluationContext<TSettings>) => SettingValidationResult;
|
||||
coerce?: (value: unknown, context: SettingEvaluationContext<TSettings>) => unknown;
|
||||
affects?: SettingEffect[];
|
||||
render?: "auto" | "custom";
|
||||
};
|
||||
```
|
||||
|
||||
`SettingEvaluationContext` should carry the current editing settings, persisted
|
||||
settings, platform capabilities, and remote capability information if known. It
|
||||
should not carry an Obsidian `App`.
|
||||
|
||||
`labelKey` and `descriptionKey` should be i18n keys. During migration, the key
|
||||
may be the literal English text already used by `configurationNames` and
|
||||
`SettingInformation`. This matches the current i18n behaviour where an unknown
|
||||
key resolves to the key itself, avoids adding translation resources up front, and
|
||||
lets us later replace literal keys with stable resource keys without changing
|
||||
consumers. `label` and `description` remain compatibility aliases while existing
|
||||
code still expects resolved strings.
|
||||
|
||||
`internal` should mark settings that are not currently editable from the UI.
|
||||
Obsolete settings are internal by default. Persisted settings without UI metadata
|
||||
are also internal until explicitly classified otherwise.
|
||||
|
||||
## Storage Domains
|
||||
|
||||
Settings should be classified by where they live:
|
||||
|
||||
- `persisted`: normal `ObsidianLiveSyncSettings` values saved through
|
||||
`SettingService`.
|
||||
- `local`: values stored outside the main settings document, for example local
|
||||
storage or device/vault identity.
|
||||
- `derived`: values computed from persisted settings, for example `syncMode`.
|
||||
- `ephemeral`: dialogue-only inputs such as `preset`.
|
||||
|
||||
This makes current special cases explicit:
|
||||
|
||||
- `configPassphrase` is local-only.
|
||||
- `deviceAndVaultName` is local/service managed.
|
||||
- `syncMode` is derived from `liveSync` and `periodicReplication`.
|
||||
- `preset` is ephemeral and expands to several persisted settings.
|
||||
|
||||
## Rendering Strategy
|
||||
|
||||
The repository should support generic rendering, but it should not force every
|
||||
pane to become schema-driven immediately.
|
||||
|
||||
Use three levels:
|
||||
|
||||
1. **Auto-rendered controls**
|
||||
Simple `boolean`, `number`, `text`, `password`, `select`, and `textarea`
|
||||
settings. These can replace many `new Setting(...).autoWire...` calls.
|
||||
|
||||
2. **Repository-defined groups with custom sections**
|
||||
A pane can declare layout, headings, and order through the repository but keep
|
||||
a custom renderer for the section body.
|
||||
|
||||
3. **Fully custom workflow panes**
|
||||
Remote configuration management, E2EE setup, setup wizard, maintenance, and
|
||||
rebuild flows should remain custom until their side effects are separately
|
||||
modelled.
|
||||
|
||||
The Obsidian setting dialogue becomes a renderer of repository definitions plus a
|
||||
host for custom workflow panes.
|
||||
|
||||
## Side Effects
|
||||
|
||||
Setting changes should distinguish value persistence from effects.
|
||||
|
||||
Examples of effects:
|
||||
|
||||
- `requires-local-rebuild`
|
||||
- `requires-remote-rebuild`
|
||||
- `requires-restart`
|
||||
- `requires-apply-settings`
|
||||
- `suspends-sync`
|
||||
- `updates-unresolved-error-ui`
|
||||
- `changes-active-remote`
|
||||
- `expands-preset`
|
||||
|
||||
The current `isNeedRebuildLocal()` and `isNeedRebuildRemote()` methods should
|
||||
eventually be replaced by repository metadata. This would make rebuild prompts
|
||||
testable without rendering the full settings tab.
|
||||
|
||||
## Capability Requirements
|
||||
|
||||
Some settings and actions require capabilities that not all users or platforms
|
||||
have.
|
||||
|
||||
Examples:
|
||||
|
||||
- CouchDB server diagnostics and automatic CouchDB repair require server-admin
|
||||
capability.
|
||||
- Normal CouchDB sync requires only database-user capability.
|
||||
- Hidden File Sync and Customisation Sync require filesystem capability.
|
||||
- Obsidian plug-in reload requires obsidian-plugin-host capability.
|
||||
- Opening settings panes and workspace views requires obsidian-shell capability.
|
||||
|
||||
Capability metadata should be used for:
|
||||
|
||||
- warning text in Obsidian settings,
|
||||
- disabling unsupported actions,
|
||||
- CLI/WebApp help output,
|
||||
- Harness tests for visibility and enabled-state rules.
|
||||
|
||||
The repository should not introduce a generic cross-platform `PluginManager`
|
||||
concept. Obsidian plug-in host behaviour should remain an Obsidian-specific
|
||||
adapter or custom workflow.
|
||||
|
||||
## Current Assumptions to Preserve
|
||||
|
||||
- Settings can be edited in a buffer before being saved.
|
||||
- Some values save immediately unless `holdValue` is set.
|
||||
- Some values require explicit Apply buttons.
|
||||
- Visibility and enabled-state often depend on other editing values.
|
||||
- Some settings are hidden in setup wizard mode.
|
||||
- Advanced, power-user, and edge-case levels remain supported.
|
||||
- The dialogue can be reloaded while preserving dirty local edits.
|
||||
- Existing `SettingService` remains responsible for encryption, persistence,
|
||||
migration, and applying settings.
|
||||
- Existing complex setup and remote configuration workflows remain custom.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
### Phase 1: Repository Skeleton
|
||||
|
||||
- Create a repository module in the shared setting domain
|
||||
(`src/lib/src/common/settings`), not under the Obsidian dialogue folder.
|
||||
- Move existing `configurationNames` metadata into repository definitions without
|
||||
changing runtime behaviour.
|
||||
- Add storage domain, kind, i18n keys, internal marker, pane, section, level, and
|
||||
secret metadata for a small subset of settings.
|
||||
- Keep `getConfig()`, `getConfName()`, and existing callers working through a
|
||||
compatibility facade.
|
||||
|
||||
### Phase 2: Evaluation API
|
||||
|
||||
- Add pure functions:
|
||||
- `getSettingDefinition(key)`
|
||||
- `listSettingDefinitions(filter)`
|
||||
- `evaluateSetting(definition, context)`
|
||||
- `validateSettingValue(key, value, context)`
|
||||
- `getSettingEffects(changedKeys, context)`
|
||||
- Add unit tests for derived values, visibility, enabled-state, validation, and
|
||||
rebuild/restart effects.
|
||||
|
||||
### Phase 3: Obsidian Renderer for Simple Controls
|
||||
|
||||
- Add a small renderer that maps repository definitions to Obsidian `Setting`
|
||||
controls.
|
||||
- Migrate one low-risk pane first, likely Appearance/Logging or Advanced memory
|
||||
cache settings.
|
||||
- Keep custom panes untouched.
|
||||
- Keep `LiveSyncSetting` as a compatibility wrapper during migration.
|
||||
|
||||
### Phase 4: Derived and Local Values
|
||||
|
||||
- Model `syncMode`, `preset`, `configPassphrase`, and `deviceAndVaultName`
|
||||
explicitly.
|
||||
- Replace ad hoc save paths in `ObsidianLiveSyncSettingTab` with storage-domain
|
||||
handlers.
|
||||
- Keep user-visible behaviour unchanged.
|
||||
|
||||
### Phase 5: Effects and Capability Warnings
|
||||
|
||||
- Replace `isNeedRebuildLocal()` and `isNeedRebuildRemote()` with
|
||||
repository-driven effect calculation.
|
||||
- Add capability metadata for CouchDB diagnostics, repair, Hidden File Sync, and
|
||||
Obsidian-only plug-in operations.
|
||||
- Use this to improve warnings for database-scoped CouchDB users and
|
||||
administrator-only actions.
|
||||
|
||||
### Phase 6: Documentation and Non-Obsidian Consumers
|
||||
|
||||
- Treat documentation as an authored source, not as an output that must be fully
|
||||
generated from code.
|
||||
- Optionally combine repository metadata with a documentation source such as YAML
|
||||
to generate or lint `docs/settings.md`.
|
||||
- Use the repository to verify that documented settings exist, that defaults and
|
||||
storage domains are consistent, and that internal settings are intentionally
|
||||
omitted or documented as internal.
|
||||
- Expose repository metadata to CLI/WebApp where useful.
|
||||
- Let Harness tests assert the same repository semantics used by Obsidian.
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
Use Harness or unit tests for:
|
||||
|
||||
- default value coverage,
|
||||
- type/kind consistency,
|
||||
- every persisted setting has a definition or is explicitly internal,
|
||||
- visibility and enabled-state predicates,
|
||||
- derived values such as `syncMode`,
|
||||
- preset expansion,
|
||||
- rebuild/restart effect calculation,
|
||||
- capability warnings.
|
||||
|
||||
Use real Obsidian E2E for:
|
||||
|
||||
- opening the actual setting tab,
|
||||
- rendering Obsidian `Setting` components,
|
||||
- setup wizard flow,
|
||||
- remote configuration workflow,
|
||||
- actual restart prompts,
|
||||
- workflows that depend on Obsidian settings shell behaviour.
|
||||
|
||||
## Consequences
|
||||
|
||||
Positive:
|
||||
|
||||
- Setting semantics are maintained in one platform-neutral place.
|
||||
- Setting semantics become testable without mounting Obsidian UI.
|
||||
- Documentation, CLI, WebApp, and Obsidian can share setting metadata where it is
|
||||
useful.
|
||||
- Capability-sensitive settings become explicit.
|
||||
- Future settings are less likely to be implemented in only one surface.
|
||||
- The Obsidian settings dialogue can be refactored incrementally.
|
||||
|
||||
Negative:
|
||||
|
||||
- There will be a temporary compatibility layer between old setting constants and
|
||||
the repository.
|
||||
- Some panes will remain custom, so the repository will not remove all UI code.
|
||||
- Definition metadata can become stale if not enforced by tests.
|
||||
- Over-generalising workflow panes would make the repository harder to maintain.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- Do not replace `SettingService` persistence in the first phase.
|
||||
- Do not make Obsidian plug-in host operations cross-platform.
|
||||
- Do not convert all setting panes to schema-driven UI at once.
|
||||
- Do not require real Obsidian E2E for every setting definition.
|
||||
- Do not remove custom renderers for remote setup, E2EE setup, or maintenance
|
||||
workflows.
|
||||
|
||||
## Open Questions
|
||||
|
||||
- What should the exact `CapabilityProvider` interface look like for static
|
||||
platform capabilities and runtime-probed remote capabilities? This should be
|
||||
decided while implementing the Obsidian renderer so the interface follows a
|
||||
real consumer instead of an abstract capability model.
|
||||
+1
-1
Submodule src/lib updated: 87dc724c9d...72a2f684d1
Reference in New Issue
Block a user