13 KiB
Activity/Sidebar/Editor Behavior Migration Plan
Goal
Create one shared behavior system for Activity Bar buttons, sidebar ownership, and editor/tab opening rules so all activities behave consistently by default.
Scope
- Renderer only (
src/renderer/**) - Activity Bar button behavior
- Sidebar switching/ownership
- Editor panel opening via tabs
- Tab policy for singleton tools (settings/tags/style/docs/etc.)
Non-goals
- Re-designing visuals
- Changing i18n copy beyond parity fixes
- Rewriting store architecture in one step
Current Behavior Exceptions (as-is)
A. Activity icon active-state exceptions
- No known per-activity active-state exceptions in
ActivityBar.- All activity icons now use
sidebar-owneractive behavior.
- All activity icons now use
B. Click behavior exceptions
- No known per-activity click behavior exceptions in
ActivityBar.- Activity buttons now follow one shared posts-style switch/toggle pattern.
- Remaining coupling is in sidebar section navigation (
SettingsNav/TagsNav) and is planned for unified section activation API migration.
C. Sidebar content exceptions
- Posts/Pages sidebars are mounted in wrappers with
display: nonewhen inactive. - Media/Settings/Tags/Chat/Import/Git sidebars are conditionally mounted.
- SettingsNav/TagsNav perform section scrolling and can auto-open corresponding tabs before scroll.
D. Editor content exceptions
- Editor is primarily tab-driven for tool views (
settings,tags,chat,import, etc.). - Sidebar ownership (
activeView) and editor content (active tab) are intentionally decoupled. - Some menu commands open tabs directly without harmonizing sidebar ownership.
E. API shape exceptions
- Multiple places directly implement activity behavior logic instead of shared rules.
- Open-tab semantics for singleton activities are duplicated across components.
Decisions (confirmed)
-
Active strategy is unified
- Keep only
sidebar-owner. - Remove
sidebar-or-tabspecial case. - Consequence: Settings icon is active only when Settings owns the visible sidebar.
- Keep only
-
Click behavior is unified to Posts behavior
- All activity buttons follow the same switch/toggle sidebar logic pattern.
import,settings, andtagsshould not have unique click semantics.- If a view needs to open/ensure a tab, that responsibility moves outside button-click special cases (e.g., explicit nav/editor actions), not activity click branching.
-
Sidebar/editor decoupling stays
- Sidebar owner (
activeView) and editor content (active tab) remain intentionally independent. - Menu actions opening editors directly are valid and remain supported.
- Sidebar owner (
-
Editor tab management should be centralized next
- Add shared tab policy/behavior layer to remove per-editor inconsistencies.
-
API-shape exception outlook
- Most API-shape exceptions should disappear as behavior is centralized.
- A few explicit policy descriptors will still remain (not exceptions): e.g., tab kind, transience defaults, singleton keys.
-
Unified section activation API is required
- Introduce one shared section activation API for editor/tool views.
- Sidebars should trigger section activation through that API instead of ad-hoc per-view scroll/open logic.
-
Sidebar mounting strategy is Variant B (conditional mount)
- Mount only the currently active sidebar; unmount inactive sidebars.
- Rationale: simplest and most consistent behavior model now.
- Performance tuning can be revisited later if real bottlenecks appear.
- Given expected limited content sizes, this is acceptable as default.
-
Sidebar section actions always activate/open corresponding editor
- Clicking a sidebar section/navigation action must always ensure the matching editor tab is active.
- If the editor tab is not open, it should be auto-opened first, then section activation should run.
Proposed Target Architecture
1) Activity Registry (single source of truth)
A declarative registry describing each activity:
idsidebarViewplacement(top/bottom)labelKey(i18n)activeStrategy(sidebar-owneronly)clickStrategy(single unified strategy for all activity buttons)
2) Shared Activity Behavior Engine
Pure functions that compute:
isActivityActive(snapshot, activityId)getActivityClickActions(snapshot, activityId)
No UI code inside this engine. Output is action descriptors consumed by UI components.
3) Reusable Render Adapters
ActivityBarrenders from registry + icon mapSidebarpicks section from registry/sidebar view mapping- Later:
Editortool-tab mapping can also be declared instead of chained conditionals
4) Shared Tab Policy Layer (new)
Pure tab-policy helpers that decide:
- singleton vs multi-instance
- transient vs pinned defaults
- promotion rules (transient -> pinned)
- when tab-open should happen from sidebar actions vs explicit editor/menu actions
Sidebar Mounting Variants: Behavior Tradeoffs
Variant A — Keep-mounted sidebars (hidden with CSS)
How it works: all sidebar components stay mounted; inactive ones are hidden.
Benefits
- Preserves in-component UI state (expanded sections, scroll position, local filters) without extra persistence code.
- Fast switching (no remount cost).
Costs
- Higher memory usage.
- More background effects/subscriptions unless each component guards itself.
- Risk of inactive sidebars still doing work.
Variant B — Conditionally mounted sidebars (mount/unmount)
How it works: only current sidebar component is mounted.
Benefits
- Lower memory/CPU baseline.
- Clear lifecycle boundaries and fewer hidden side effects.
- Simpler mental model for ownership.
Costs
- Local UI state resets unless persisted explicitly.
- Potentially more reload work when switching back.
Variant C — Hybrid (current)
How it works: some sidebars keep-mounted, others conditional.
Benefits
- Tactical optimization where needed.
Costs
- Inconsistent behavior and harder debugging.
- More “why is this one different?” maintenance overhead.
Recommendation for consistency
- Pick A or B globally.
- If preserving local sidebar UI state is important, pick A.
- If simplicity + lower background work is the priority, pick B.
- Avoid C long-term.
Phased Migration Plan
Phase 0 — Baseline Contract Tests (safety net)
- Add behavior tests that lock target UX for all activities.
- Remove/replace tests that validate deprecated exceptions.
- Outcome: refactor-safe baseline.
Phase 1 — Extract Activity Behavior Core (start here)
- Add shared activity behavior module + typed activity registry for activity buttons.
- Migrate
ActivityBarto use shared behavior engine. - Keep visible behavior unchanged.
- Add/adjust tests for helper +
ActivityBarintegration.
Phase 1b — Enforce Unified Activity Rules (new)
- Remove
sidebar-or-tabfrom activity behavior. - Make settings active-state
sidebar-owneronly. - Unify click behavior for
import/settings/tagsto posts pattern. - Update/expand contract tests for unified activity behavior and
ActivityBarintegration.
Phase 2 — Normalize Sidebar Mounting + Ownership Mapping
- Introduce a sidebar view registry mapping in one place.
- Pick one global mounting strategy (A or B) and apply to all sidebars.
- Add explicit state persistence for remount-safe sidebar section UX where needed.
- Introduce unified section activation API and migrate
SettingsNav/TagsNavto use it.
Phase 3 — Centralize Editor/Tab Management (expanded)
- Implement shared tab policy layer for singleton tool tabs.
- Centralize singleton tab policies (
settings,tags,style,documentation,metadata-diff,site-validation). - Centralize post/media transient-vs-pinned open intent rules via shared entity-tab policy helper.
- Remove duplicated tab-open logic from sidebars/nav/components for centralized policy-covered tab types.
- Ensure site-validation tab reopens with persisted last report and refreshes only on explicit validation trigger.
- Centralize multi-instance tab opening policies for
chat,import, andgit-diff(file/commit resources).
Editor Tab Management Variations (current analysis)
- Singleton, pinned tool tabs
settings,tags,style,documentationtypically open as non-transient singleton tabs.
- Ephemeral/transient tabs with promotion
- Post/media browsing flows can open transient tabs that become pinned on explicit user action (double-click or pin).
- Multi-instance tool tabs
chatandimportcreate one tab per entity ID (conversation/definition), usually non-transient.
- Derived-ID utility tabs
git-diffuses structured IDs (git-diff:<resource>/ commit variants), with special title derivation.
- Menu-opened utility tabs
metadata-diff,site-validation, and others are opened from menu commands with their own transience defaults.
Standardization target for tab management
- One registry describing for each tab type:
instanceMode:singleton | per-entity | per-resourcedefaultTransient:true | falsepromotable:true | falseidStrategy: canonical ID normalization/resolutiontitleStrategy: shared title resolver hooks
Phase 4 — Editor Routing Registry
- Replace long
if/elsetab-type rendering chain with declarative tab->component mapping. - Keep special cases explicit (e.g., tab ID transforms such as git-diff commit/file parsing).
Phase 5 — Menu/Event Harmonization
- Route menu-driven view/tab actions through the same behavior layer where applicable.
- Reduce drift between keyboard/menu/toolbar interactions.
Phase 6 — Cleanup + Exception Review
- Reassess every exception in this file and decide keep/remove.
- Remove dead paths and duplicate helpers.
- Update tests and docs.
Decision Checklist (updated)
- Remove
sidebar-or-tab; usesidebar-owneronly. - Unify activity click behavior to posts pattern for all activities.
- Keep sidebar/editor decoupling.
- Implement a unified section activation API for editor/tool sections.
- Choose global sidebar mounting variant: B conditional mount.
- Define sidebar action rule: section clicks always activate/open corresponding editor.
- Migrate existing
SettingsNav/TagsNavauto-open + delayed scroll to the unified section activation API that enforces this rule.
Work Log
- Documented migration strategy and current exception matrix.
- Phase 1 slice complete: shared activity behavior extracted and wired into
ActivityBar. - Decisions captured: unified active strategy, unified click behavior, editor/sidebar decoupling retained.
- Decision captured: unified section activation API required.
- Decision captured: sidebar mounting strategy Variant B (conditional mount).
- Decision captured: sidebar section actions always activate/open corresponding editor.
- Phase 1b complete: removed
sidebar-or-tab, unified activity click behavior, and updated tests. - Added
Edit Preferencesmenu item (CmdOrCtrl+,) to open preferences editor directly. - Phase 2 slice complete: added shared section activation API and migrated
SettingsNav/TagsNav. - Phase 2 slice complete: normalized posts/pages sidebar rendering to Variant B conditional mount.
- Phase 2 slice complete: introduced canonical sidebar view registry and applied it across navigation/store/sidebar mapping.
- Phase 2 slice complete: persisted settings/tags active section selection for remount-safe UX.
- Phase 3 slice complete: added singleton tab policy helper and migrated menu/sidebar singleton tool openings to it.
- Phase 3 slice complete: site validation report is persisted per project and reloaded by
SiteValidationView. - Phase 3 slice complete: site validation view refreshes via explicit
bds:site-validation-updatedevent (no auto-validate on mount). - Phase 3 continuation slice complete: added shared entity tab intent helper (
preview/pin) forpost/media. - Phase 3 continuation slice complete: migrated post/media open paths in Sidebar, Editor, LinkedMediaPanel, and Panel to shared entity-tab policy.
- Phase 3 completion slice: centralized
chatandimporttab-open specs and migrated sidebar call sites. - Phase 3 completion slice: centralized
git-difffile/commit ID/spec/open helpers and reused shared parsing inTabBar. - Phase 4 complete: introduced
editorRoutingregistry/resolver and migratedEditorto declarative route->view rendering. - Phase 5 complete: introduced shared activity action executor and reused it in
ActivityBarand menu view handlers. - Phase 5 complete:
menu:viewPostsandmenu:viewMedianow follow the same action pipeline as activity-bar clicks. - Next implementation slice: Phase 6 (cleanup + exception review).