34 KiB
CMS Wiring: Settings, AppConfig, Memory, EditLog Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Replace zesdex-backend's use of zesdex_entities::seaorm::common::{settings,app_config,memory,edit_log} (inherent-method I/O) with the previously-orphaned zesdex-cms crate's repository-based equivalents (JsonSettingsRepository, JsonAppConfigRepository, MarkdownMemoryRepository, JsonlEditLogRepository), which are on-disk-format-compatible drop-ins. Fix the one real defect found in zesdex-cms::Settings along the way (a missing #[serde(default)] that would hard-fail loading any pre-existing settings.json).
Architecture: zesdex-backend call sites move from Type::static_method() to repository_instance.method(&base_dir, ...). Since zesdex-cms's application-service layer (SettingsServiceImpl, MemoryServiceImpl) doesn't cover every read pattern the backend needs (no bare AppConfig getter, no single-memory read, no EditLogService at all), most call sites construct and use the concrete Json*Repository/MarkdownMemoryRepository/JsonlEditLogRepository types directly rather than going through the service traits — this matches the actual usage shape better than forcing everything through an ill-fitting service abstraction.
Tech Stack: Rust, Cargo workspace (zesdex-cms, zesdex-backend, zesdex-entities).
Global Constraints
- On-disk formats are confirmed compatible for all four entities (same JSON/markdown/JSONL shape and file paths) — this plan is a call-site migration, not a data migration. No existing user
settings.json/app_config.json/*.mdmemory files/edits.jsonlneed to change. EditLog's domain shape genuinely differs between the old (entries+path, disk I/O on construction) and new (entriesonly, disk I/O viaEditLogRepository::open) versions — every call site becomes fallible (Result) where it was previously infallible.- No new
#[allow(...)]attributes. Existing ones in touched files are out of scope (handled by2026-07-16-convention-cleanup-docs.md). - Tests are inline
#[cfg(test)] mod tests, per CLAUDE.md. - Run
cargo test --workspaceandcargo clippy --workspace --all-targets -- -D warningsbefore each commit. - Base directory resolution: everywhere the old code called the zero-argument
Settings::load()/Settings::save()/AppConfig::load()(which internally calledzesdex_entities::seaorm::common::store::Store::new()to getbase_dir), the replacement must callzesdex_entities::seaorm::common::store::Store::new().base_direxplicitly and pass it to thezesdex-cmsrepository — this reproduces the exact same directory, verified identical during planning.
Task 1: Fix the hive_mind_node_timeout_ms serde-default gap in zesdex-cms::Settings
Context: crates/zesdex-entities/src/seaorm/common/settings.rs:75 has #[serde(default = "default_hive_mind_node_timeout_ms")] on this field; crates/zesdex-cms/src/domain/settings.rs's copy does not. Without this, any settings.json written before this field existed (or any settings.json missing it for any reason) will hard-fail JsonSettingsRepository::load with a parse error, whereas the old Settings::load() silently defaulted on any failure. Fix both the missing default and restore full parse-failure tolerance to avoid a behavior regression for existing users.
Files:
- Modify:
crates/zesdex-cms/src/domain/settings.rs - Modify:
crates/zesdex-cms/src/infrastructure/persistence/settings_repo.rs
Interfaces:
-
Produces:
Settings::default()unchanged in value;JsonSettingsRepository::loadbecomes tolerant of parse failures (stillResult-returning, but only returnsErrfor I/O errors other than "not found" or "malformed JSON" — matching the old infallible-except-I/O-permission-errors behavior as closely as aResult-based API can). -
Step 1: Write the failing test
Add to crates/zesdex-cms/src/infrastructure/persistence/settings_repo.rs's #[cfg(test)] mod tests (create it if absent — check first: grep -n "mod tests" crates/zesdex-cms/src/infrastructure/persistence/settings_repo.rs):
#[test]
fn load_defaults_hive_mind_timeout_when_field_missing_from_old_settings_json() {
let dir = std::env::temp_dir().join(format!("zesdex-cms-settings-test-{}", uuid::Uuid::new_v4()));
std::fs::create_dir_all(&dir).unwrap();
// Simulate a settings.json written before `hive_mind_node_timeout_ms` existed.
std::fs::write(
dir.join("settings.json"),
r#"{"internet_mode":"Off","provider":"zen","model":"m","api_keys":{},"max_tokens":null,"temperature":null,"review_max_lessons_per_run":5,"adaptive_review_max_skip":3,"verify_command":null,"verify_timeout_ms":30000,"workflow_max_concurrency":5,"review_enabled":true,"session_archive_enabled":true,"lsp_auto_provision":true,"lsp_languages":[]}"#,
).unwrap();
let repo = JsonSettingsRepository::new();
let settings = repo.load(&dir).expect("load must not fail on a pre-existing settings.json missing the new field");
assert_eq!(settings.hive_mind_node_timeout_ms, 600_000);
let _ = std::fs::remove_dir_all(&dir);
}
(Requires uuid as a dev-dependency of zesdex-cms — check first: grep uuid crates/zesdex-cms/Cargo.toml; add uuid = { workspace = true } under [dev-dependencies] if missing, creating that section if it doesn't exist.)
- Step 2: Run the test to verify it fails
Run: cargo test -p zesdex-cms load_defaults_hive_mind -- --nocapture
Expected: fails with a JSON parse/missing-field error.
- Step 3: Add the serde default to the domain type
In crates/zesdex-cms/src/domain/settings.rs, add above the Settings struct:
fn default_hive_mind_node_timeout_ms() -> u64 {
600_000
}
And annotate the field:
#[serde(default = "default_hive_mind_node_timeout_ms")]
pub hive_mind_node_timeout_ms: u64,
- Step 4: Restore full parse-failure tolerance in the repository
In crates/zesdex-cms/src/infrastructure/persistence/settings_repo.rs, update load:
fn load(&self, base_dir: &Path) -> Result<Settings> {
let path = base_dir.join("settings.json");
match std::fs::read_to_string(&path) {
Ok(s) => match serde_json::from_str(&s) {
Ok(settings) => Ok(settings),
Err(e) => {
tracing::warn!(
"settings.json at '{}' failed to parse ({e}); falling back to defaults",
path.display()
);
Ok(Settings::default())
}
},
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
tracing::info!("settings.json not found, using defaults");
Ok(Settings::default())
}
Err(e) => Err(anyhow::anyhow!("failed to read settings.json: {e}")),
}
}
- Step 5: Run the test to verify it passes
Run: cargo test -p zesdex-cms load_defaults_hive_mind -- --nocapture
Expected: pass.
- Step 6: Run the crate's full test suite and clippy
Run: cargo test -p zesdex-cms && cargo clippy -p zesdex-cms -- -D warnings
Expected: all pass, no new warnings.
- Step 7: Commit
git add crates/zesdex-cms/src/domain/settings.rs crates/zesdex-cms/src/infrastructure/persistence/settings_repo.rs
git commit -m "fix(cms): perbaiki serde default hive_mind_node_timeout_ms & toleransi parse gagal di Settings"
Task 2: Swap Settings call sites to JsonSettingsRepository
Files (every one confirmed by research — swap all):
crates/zesdex-backend/src/app/state/rest.rs(lines 20, 49, 89, 103, 170)crates/zesdex-backend/src/bin/seed.rs(line 12)crates/zesdex-backend/src/app/subagent/engine.rs(lines 61, 64-84, 353 doc comment)crates/zesdex-backend/src/app/workflow/hive_mind.rs(lines 255, 281-283, 367 doc comments)crates/zesdex-backend/src/app/runtime/context/window.rs(lines 9, 19-25, 46/56/79 test-only)crates/zesdex-backend/src/app/mode/settings.rs(lines 6, 16-22)crates/zesdex-backend/src/controller/input.rs(lines 339, 354, 357-361, 393-407)crates/zesdex-backend/src/view/mod.rs(lines 152-174, 701-713 — read-only, no method-call change needed beyond the type import)crates/zesdex-backend/src/view/status.rs(lines 68, 86-93 — read-only)crates/zesdex-backend/src/app/review/mod.rs(lines 79, 85, 405-406 — read-only)crates/zesdex-backend/src/app/runtime/actions/mod.rs(lines 549, 633-671, 1615, 1758 — read-only)crates/zesdex-backend/src/main.rs(lines 141, 474, 641 —.save()calls)
Interfaces:
-
Consumes:
zesdex_cms::domain::settings::Settings,zesdex_cms::infrastructure::persistence::settings_repo::JsonSettingsRepository,zesdex_cms::domain::repository::SettingsRepository(trait, for method resolution),zesdex_entities::seaorm::common::store::Store(forbase_dirresolution). -
Produces:
AppStateRest.settings: zesdex_cms::domain::settings::Settings(type changed from the old entities type — field-identical, so every read-only call site above needs only an import-path change, not a logic change). -
Step 1: Update the type import everywhere it's read-only
In each of rest.rs, view/mod.rs, view/status.rs, app/review/mod.rs, app/runtime/actions/mod.rs, replace:
use crate::model::settings::Settings;
with:
use zesdex_cms::domain::settings::Settings;
(For files that reference Settings only via state.settings.<field> without an explicit use for the type itself — confirm per-file with grep -n "use.*settings::Settings\|model::settings" <file> before editing — skip files where no explicit import exists, since AppStateRest.settings's type change alone (Step 3 below) is what they actually depend on.)
- Step 2: Update
app/mode/settings.rs(mutatesSettingsin place, no I/O)
Read the file first: cat crates/zesdex-backend/src/app/mode/settings.rs. Replace:
use crate::model::settings::{InternetMode, Settings};
with:
use zesdex_cms::domain::settings::{InternetMode, Settings};
cycle_internet_mode(settings: &mut Settings)'s body (mutating settings.internet_mode in a cycle) needs no logic change — InternetMode is field-identical between old and new.
- Step 3: Update
AppStateRestconstruction and field type
In crates/zesdex-backend/src/app/state/rest.rs:
Replace the import (line 20):
use crate::model::settings::Settings;
with:
use zesdex_cms::domain::settings::Settings;
use zesdex_cms::domain::repository::SettingsRepository;
use zesdex_cms::infrastructure::persistence::settings_repo::JsonSettingsRepository;
Replace the field's type comment reference (line 49) — no change needed, pub settings: Settings, already resolves to the new import.
Replace construction (line 89):
let settings = Settings::load();
with:
let store_base_dir = zesdex_entities::seaorm::common::store::Store::new().base_dir;
let settings = JsonSettingsRepository::new()
.load(&store_base_dir)
.unwrap_or_default();
- Step 4: Update
controller/input.rs's.save()call sites
Read the file first: grep -n -B3 "\.settings\.save()" crates/zesdex-backend/src/controller/input.rs
Both call sites (previously lines 361 and 407) currently do let _ = state.settings.save();. Since Settings no longer carries an inherent save() method, replace each with:
let _ = zesdex_cms::infrastructure::persistence::settings_repo::JsonSettingsRepository::new()
.save(&state.store_base_dir(), &state.settings);
(Uses state.store_base_dir() — the existing helper on AppStateRest, confirmed to resolve to the same directory as Store::new().base_dir in normal operation — since these call sites already have state: &mut AppStateRest in scope, unlike rest.rs::new() which doesn't yet have a constructed state to call .store_base_dir() on.)
Add use zesdex_cms::domain::repository::SettingsRepository; to this file's imports if not already present after Step 1.
- Step 5: Update
main.rs's three.save()call sites
Read the file first: grep -n -B2 "\.settings\.save()" crates/zesdex-backend/src/main.rs
Replace each let _ = state.settings.save(); (or client_state.settings.save()) with the same pattern as Step 4, substituting the correct state variable name at each site:
let _ = zesdex_cms::infrastructure::persistence::settings_repo::JsonSettingsRepository::new()
.save(&state.store_base_dir(), &state.settings);
- Step 6: Update
app/subagent/engine.rs'sresolve_provider_config()
Read the function first: grep -n -A 30 "fn resolve_provider_config" crates/zesdex-backend/src/app/subagent/engine.rs
Replace:
let settings = crate::model::settings::Settings::load();
with:
let store_base_dir = zesdex_entities::seaorm::common::store::Store::new().base_dir;
let settings = zesdex_cms::infrastructure::persistence::settings_repo::JsonSettingsRepository::new()
.load(&store_base_dir)
.unwrap_or_default();
(Add use zesdex_cms::domain::repository::SettingsRepository; to this file's imports.) The subsequent field reads (settings.api_keys, settings.provider, settings.model at lines 64-84) need no change — same field names/types.
- Step 7: Update
app/workflow/hive_mind.rs'srun_hive_mind()
Read the function first: grep -n -A 5 "let settings = crate::model::settings::Settings::load" crates/zesdex-backend/src/app/workflow/hive_mind.rs
Apply the identical substitution pattern from Step 6. Field reads settings.hive_mind_node_timeout_ms/settings.workflow_max_concurrency need no change.
- Step 8: Update
app/runtime/context/window.rs
Read the file first: cat crates/zesdex-backend/src/app/runtime/context/window.rs
Replace the import (line 9):
use crate::model::settings::Settings;
with:
use zesdex_cms::domain::settings::Settings;
The resolve(app_config: &AppConfig, settings: &Settings) function signature/body and the three test-only Settings::default() constructions need no logic change — same type shape, Default still works identically after Task 1.
- Step 9: Update
bin/seed.rs
Read the file first: cat crates/zesdex-backend/src/bin/seed.rs
Replace:
zesdex_entities::seaorm::common::settings::Settings::default()
with:
zesdex_cms::domain::settings::Settings::default()
- Step 10: Remove the now-unused
model::settingsre-export
In crates/zesdex-backend/src/model/mod.rs, remove:
pub mod settings {
pub use zesdex_entities::seaorm::common::settings::*;
}
- Step 11: Verify no remaining references
Run: grep -rn "model::settings::" crates/zesdex-backend/src
Expected: no output.
- Step 12: Build and test
Run: cargo build --workspace && cargo test --workspace
Expected: no errors, all tests pass.
- Step 13: Manual smoke test
Run: cargo run -p zesdex-backend. Confirm the TUI starts, the Settings overlay (per view/mod.rs) displays the current provider/model/flags correctly, and changing internet mode / API key / provider / model persists correctly across a restart (settings.json is written and re-read with the same values).
- Step 14: Commit
git add -A
git commit -m "refactor(backend): alihkan Settings ke zesdex-cms JsonSettingsRepository"
Task 3: Swap AppConfig call sites to JsonAppConfigRepository
Files:
crates/zesdex-backend/src/app/state/rest.rs(lines 18, 50, 90, 104)crates/zesdex-backend/src/bin/seed.rs(line 27)crates/zesdex-backend/src/app/subagent/engine.rs(lines 62, 69, 73)crates/zesdex-backend/src/app/runtime/context/window.rs(lines 8, 19-25, test sites)crates/zesdex-backend/src/controller/input.rs(lines 220, 252, 383, 385)crates/zesdex-backend/src/view/mod.rs(lines 710-711)crates/zesdex-backend/src/view/status.rs(line 68)crates/zesdex-backend/src/app/runtime/actions/mod.rs(lines 548, 551, 635-659, 1755-1758)
Interfaces:
-
Consumes:
zesdex_cms::domain::app_config::{AppConfig, ProviderConfig, ModelRole},zesdex_cms::infrastructure::persistence::app_config_repo::JsonAppConfigRepository,zesdex_cms::domain::repository::AppConfigRepository. -
Produces:
AppStateRest.app_config: zesdex_cms::domain::app_config::AppConfig(type changed, field-identical).AppConfigis never saved anywhere inzesdex-backendtoday (confirmed by research) — this task only needsload, nosavecall sites. -
Step 1: Update read-only imports
Same pattern as Task 2 Step 1: in each file that imports crate::model::app_config::AppConfig/ProviderConfig, replace with zesdex_cms::domain::app_config::{AppConfig, ProviderConfig} (add ModelRole too where view/mod.rs/window.rs need it).
- Step 2: Update
AppStateRestconstruction
In crates/zesdex-backend/src/app/state/rest.rs, add to the import block from Task 2 Step 3:
use zesdex_cms::domain::app_config::AppConfig;
use zesdex_cms::domain::repository::AppConfigRepository;
use zesdex_cms::infrastructure::persistence::app_config_repo::JsonAppConfigRepository;
Replace construction (line 90):
let app_config = AppConfig::load();
with:
let app_config = JsonAppConfigRepository::new()
.load(&store_base_dir)
.unwrap_or_default();
(Reuses the store_base_dir local variable already introduced in Task 2 Step 3 — both Settings and AppConfig load from the same base directory.)
- Step 3: Update
app/subagent/engine.rs
Apply the same substitution as Task 2 Step 6, adding the AppConfig load right after the Settings load using the same store_base_dir:
let app_config = zesdex_cms::infrastructure::persistence::app_config_repo::JsonAppConfigRepository::new()
.load(&store_base_dir)
.unwrap_or_default();
(Add use zesdex_cms::domain::repository::AppConfigRepository;.)
- Step 4: Update
bin/seed.rs
Replace zesdex_entities::seaorm::common::app_config::AppConfig::default() with zesdex_cms::domain::app_config::AppConfig::default().
- Step 5: Remove the now-unused
model::app_configre-export
In crates/zesdex-backend/src/model/mod.rs, remove:
pub mod app_config {
pub use zesdex_entities::seaorm::common::app_config::*;
}
- Step 6: Verify no remaining references, build, and test
Run: grep -rn "model::app_config::" crates/zesdex-backend/src — expected no output.
Run: cargo build --workspace && cargo test --workspace — expected all pass.
- Step 7: Manual smoke test
Run the TUI, open the Model Selector overlay (Ctrl+P or equivalent per resources.rs), confirm the provider/model list still populates correctly from app_config.json, and that Claude-credential auto-detection (if ~/.claude/settings.json exists on the test machine) still merges in correctly.
- Step 8: Commit
git add -A
git commit -m "refactor(backend): alihkan AppConfig ke zesdex-cms JsonAppConfigRepository"
Task 4: Swap Memory call sites to MarkdownMemoryRepository
Files:
crates/zesdex-backend/src/tool/memory/recall.rs(lines 4, 44, 63, 69)crates/zesdex-backend/src/tool/memory/remember.rs(lines 4, 74, 79-96)crates/zesdex-backend/src/tool/memory/forget.rs(lines 4, 45)crates/zesdex-backend/src/app/mode/learning.rs(lines 56, 58)crates/zesdex-backend/src/app/workflow/docs.rs(line 34 —slugifyonly, no I/O)crates/zesdex-backend/src/app/review/mod.rs(lines 487, 491, 493-494)crates/zesdex-backend/src/app/runtime/actions/mod.rs(lines 591, 797, 810, 833, 843)
Interfaces:
-
Consumes:
zesdex_cms::domain::memory::Memory,zesdex_cms::infrastructure::persistence::memory_repo::MarkdownMemoryRepository,zesdex_cms::domain::repository::MemoryRepository. -
Produces: nothing new for other tasks —
Memoryis a leaf entity with no state-struct field. -
Step 1:
tool/memory/recall.rs
Read the file: cat crates/zesdex-backend/src/tool/memory/recall.rs
Replace:
use crate::model::memory::Memory;
with:
use zesdex_cms::domain::memory::Memory;
use zesdex_cms::domain::repository::MemoryRepository;
use zesdex_cms::infrastructure::persistence::memory_repo::MarkdownMemoryRepository;
Replace (line 44): Memory::read(&ctx.memory_dir, name) → MarkdownMemoryRepository::new().load(&ctx.memory_dir, name)
Replace (line 63): Memory::list(&ctx.memory_dir) → MarkdownMemoryRepository::new().list(&ctx.memory_dir)
Replace (line 69): Memory::read(&ctx.memory_dir, name) → MarkdownMemoryRepository::new().load(&ctx.memory_dir, name)
(Both old inherent methods and the new repository methods return Result<Memory>/Result<Vec<String>> respectively — signature shape at the call site is unchanged beyond the receiver.)
- Step 2:
tool/memory/remember.rs
Read the file: cat crates/zesdex-backend/src/tool/memory/remember.rs
Same import swap as Step 1. Replace (line 74): Memory::slugify(name) → Memory::slugify(name) (unchanged — slugify remains an inherent method on the domain struct in zesdex-cms, per research). The struct-literal construction (lines 79-92) needs no change (field-identical). Replace (lines 94-96):
memory.write(&ctx.memory_dir)?;
with:
MarkdownMemoryRepository::new().save(&ctx.memory_dir, &memory)?;
- Step 3:
tool/memory/forget.rs
Same import swap. Replace (line 45): Memory::remove(&ctx.memory_dir, name) → MarkdownMemoryRepository::new().delete(&ctx.memory_dir, name).
- Step 4:
app/mode/learning.rs
Read the file: cat crates/zesdex-backend/src/app/mode/learning.rs
Replace (line 56): crate::model::memory::Memory::list(&state.memory_dir) → zesdex_cms::infrastructure::persistence::memory_repo::MarkdownMemoryRepository::new().list(&state.memory_dir)
Replace (line 58): crate::model::memory::Memory::read(&state.memory_dir, &name) → zesdex_cms::infrastructure::persistence::memory_repo::MarkdownMemoryRepository::new().load(&state.memory_dir, &name)
(Add use zesdex_cms::domain::repository::MemoryRepository; at the top of the file.)
- Step 5:
app/workflow/docs.rs
Replace (line 34): crate::model::memory::Memory::slugify(user_request) → zesdex_cms::domain::memory::Memory::slugify(user_request). (Pure filename-generation helper, no repository/I/O involved — no other change needed.)
- Step 6:
app/review/mod.rs
Read the surrounding code: grep -n -B2 -A8 "model::memory::Memory::list(memory_dir)" crates/zesdex-backend/src/app/review/mod.rs
Replace (line 487): crate::model::memory::Memory::list(memory_dir) → zesdex_cms::infrastructure::persistence::memory_repo::MarkdownMemoryRepository::new().list(memory_dir)
Replace (line 491): crate::model::memory::Memory::read(memory_dir, &name) → zesdex_cms::infrastructure::persistence::memory_repo::MarkdownMemoryRepository::new().load(memory_dir, &name)
Replace (lines 493-494):
mem.lifecycle = "stale".to_string();
mem.write(memory_dir)?;
with:
mem.lifecycle = "stale".to_string();
zesdex_cms::infrastructure::persistence::memory_repo::MarkdownMemoryRepository::new().save(memory_dir, &mem)?;
- Step 7:
app/runtime/actions/mod.rs
Read each site first: grep -n -B2 -A2 "model::memory::Memory::" crates/zesdex-backend/src/app/runtime/actions/mod.rs
Apply the same Memory::method(dir, ...) → MarkdownMemoryRepository::new().method(dir, ...) substitution at all 5 remaining sites (lines 591, 797, 810, 833, 843), matching the method-name mapping: remove→delete, list→list, read→load.
- Step 8: Add
usestatements
Add use zesdex_cms::domain::repository::MemoryRepository; and use zesdex_cms::infrastructure::persistence::memory_repo::MarkdownMemoryRepository; to app/review/mod.rs and app/runtime/actions/mod.rs (both already import many things — add alongside existing use block).
- Step 9: Remove the now-unused
model::memoryre-export
In crates/zesdex-backend/src/model/mod.rs, remove:
pub mod memory {
pub use zesdex_entities::seaorm::common::memory::*;
}
- Step 10: Verify no remaining references, build, test
Run: grep -rn "model::memory::" crates/zesdex-backend/src — expected no output.
Run: cargo build --workspace && cargo test --workspace — expected all pass.
- Step 11: Manual smoke test
In the TUI, invoke the remember tool to create a memory, recall it back, confirm the Learning overlay lists it, then forget it and confirm it's gone. Also confirm existing .md memory files from before this change (if any test fixtures exist) still load correctly (format compatibility check).
- Step 12: Commit
git add -A
git commit -m "refactor(backend): alihkan Memory ke zesdex-cms MarkdownMemoryRepository"
Task 5: Swap EditLog call sites to JsonlEditLogRepository
Context: This is the most invasive of the four because every call site currently does EditLog::new(dir) (infallible, eager full-file read) and the new equivalent JsonlEditLogRepository::open(dir) returns Result. There are 7 distinct call sites plus one persistent field on AppStateRest.
Files:
crates/zesdex-backend/src/app/state/rest.rs(lines 19, 58, 115)crates/zesdex-backend/src/main.rs(line 236 — readsstate.edit_log.len(), no change needed beyond the type)crates/zesdex-backend/src/app/subagent/engine.rs(lines 571-580)crates/zesdex-backend/src/app/mode/rewind.rs(lines 103-114, 128-134)crates/zesdex-backend/src/app/runtime/actions/mod.rs(lines 921, 1473-1475, 1487-1489, 1586-1597)crates/zesdex-backend/src/model/mod.rs(remove re-export)
Interfaces:
-
Consumes:
zesdex_cms::domain::edit_log::{EditLog, EditLogEntry},zesdex_cms::infrastructure::persistence::edit_log_repo::JsonlEditLogRepository,zesdex_cms::domain::repository::EditLogRepository. -
Produces:
AppStateRest.edit_log: zesdex_cms::domain::edit_log::EditLog(type changed — nopathfield this time, so anything readingstate.edit_log.pathwould break; confirmed by research that no call site does this). -
Step 1: Update
AppStateRest
In crates/zesdex-backend/src/app/state/rest.rs, replace the import (line 19):
use crate::model::editlog::EditLog;
with:
use zesdex_cms::domain::edit_log::EditLog;
use zesdex_cms::domain::repository::EditLogRepository;
use zesdex_cms::infrastructure::persistence::edit_log_repo::JsonlEditLogRepository;
Replace construction (line 115):
edit_log: EditLog::new(session_dir),
with:
edit_log: JsonlEditLogRepository::new().open(session_dir).unwrap_or_else(|e| {
tracing::warn!("[state] failed to open edit log at '{}': {e}", session_dir.display());
EditLog::new()
}),
- Step 2:
app/subagent/engine.rs
Read the site: grep -n -B3 -A3 "EditLog::new(&ctx.session_dir)" crates/zesdex-backend/src/app/subagent/engine.rs
Replace:
let mut el = crate::model::editlog::EditLog::new(&ctx.session_dir);
el.append(entry).ok();
with:
let repo = zesdex_cms::infrastructure::persistence::edit_log_repo::JsonlEditLogRepository::new();
if let Ok(mut el) = repo.open(&ctx.session_dir) {
let _ = repo.append(&ctx.session_dir, &mut el, entry);
}
- Step 3:
app/mode/rewind.rs— logging the rewind operation
Read the site: grep -n -B3 -A10 "EditLog::new(&state.session_dir)" crates/zesdex-backend/src/app/mode/rewind.rs
Replace the first site (previously lines 103-114):
let mut el = crate::model::editlog::EditLog::new(&state.session_dir);
let entry = crate::model::editlog::EditLogEntry { /* ... */ };
let _ = el.append(entry);
with:
let repo = zesdex_cms::infrastructure::persistence::edit_log_repo::JsonlEditLogRepository::new();
if let Ok(mut el) = repo.open(&state.session_dir) {
let entry = zesdex_cms::domain::edit_log::EditLogEntry { /* same field values as before */ };
let _ = repo.append(&state.session_dir, &mut el, entry);
}
(Keep the exact same EditLogEntry field values from the original code — only the type path and the append mechanism change.)
- Step 4:
app/mode/rewind.rs—find_edit_path
Read the site: grep -n -B3 -A8 "fn find_edit_path" crates/zesdex-backend/src/app/mode/rewind.rs
Replace:
let el = crate::model::editlog::EditLog::new(&state.session_dir);
with:
let el = zesdex_cms::infrastructure::persistence::edit_log_repo::JsonlEditLogRepository::new()
.open(&state.session_dir)
.unwrap_or_else(|_| zesdex_cms::domain::edit_log::EditLog::new());
The subsequent el.entries.iter().rev().find(...) (unchanged — entries is a public field on both old and new EditLog) needs no further change.
- Step 5:
app/runtime/actions/mod.rs— turn-start snapshot
Read the site: grep -n -B2 -A2 "let initial_edits" crates/zesdex-backend/src/app/runtime/actions/mod.rs
Replace:
let initial_edits = crate::model::editlog::EditLog::new(&tc.edit_log_session_dir).len();
with:
let initial_edits = zesdex_cms::infrastructure::persistence::edit_log_repo::JsonlEditLogRepository::new()
.open(&tc.edit_log_session_dir)
.map(|el| el.entries.len())
.unwrap_or(0);
(EditLog in zesdex-cms has no .len() inherent method — check first: grep -n "fn len\|impl EditLog" crates/zesdex-cms/src/domain/edit_log.rs. If .len() doesn't exist, use .entries.len() as shown; if it does exist, use el.len() instead for consistency with the rest of the codebase's naming.)
- Step 6:
app/runtime/actions/mod.rs— turn-end diff
Read the site: grep -n -B2 -A10 "let final_edits" crates/zesdex-backend/src/app/runtime/actions/mod.rs
Replace:
let el = crate::model::editlog::EditLog::new(&tc.edit_log_session_dir);
let final_edits = el.len();
let total_edits_this_turn = final_edits.saturating_sub(initial_edits);
with:
let el = zesdex_cms::infrastructure::persistence::edit_log_repo::JsonlEditLogRepository::new()
.open(&tc.edit_log_session_dir)
.unwrap_or_else(|_| zesdex_cms::domain::edit_log::EditLog::new());
let final_edits = el.entries.len();
let total_edits_this_turn = final_edits.saturating_sub(initial_edits);
And immediately after (previously lines 1487-1489), the .entries.iter().skip(initial_edits) loop needs no change — same field access.
- Step 7:
app/runtime/actions/mod.rs—execute_one_tool's primary write path
Read the site: grep -n -B3 -A5 "EditLog::new(session_dir)" crates/zesdex-backend/src/app/runtime/actions/mod.rs
Apply the same pattern as Step 2 (open + conditional append via the repository).
- Step 8: Remove the now-unused
model::editlogre-export
In crates/zesdex-backend/src/model/mod.rs, remove:
pub mod editlog {
pub use zesdex_entities::seaorm::common::edit_log::*;
}
- Step 9: Verify no remaining references
Run: grep -rn "model::editlog::" crates/zesdex-backend/src
Expected: no output.
- Step 10: Build and test
Run: cargo build --workspace && cargo test --workspace
Expected: no errors, all tests pass.
- Step 11: Manual smoke test
In the TUI, make a file edit via the edit/write tool, confirm state.edit_log grows and main.rs's IPC edit_count payload reflects it, then use the rewind overlay to confirm find_edit_path still correctly recovers the edited file's path and the rewind itself works end-to-end.
- Step 12: Commit
git add -A
git commit -m "refactor(backend): alihkan EditLog ke zesdex-cms JsonlEditLogRepository"
Task 6: Delete the now-dead zesdex_entities::seaorm::common::{settings,app_config,memory,edit_log} modules
Files:
- Delete:
crates/zesdex-entities/src/seaorm/common/settings.rs,app_config.rs,memory.rs,edit_log.rs - Modify:
crates/zesdex-entities/src/seaorm/common/mod.rs(remove their module declarations)
Interfaces: none — pure deletion after Tasks 2-5 have removed every reference.
- Step 1: Verify zero remaining references across the whole workspace
Run: grep -rln "seaorm::common::settings\|seaorm::common::app_config\|seaorm::common::memory\b\|seaorm::common::edit_log" crates --include='*.rs'
Expected: no output. (If anything other than the mod.rs declaration itself shows up, stop and investigate before deleting — it means a call site was missed in Tasks 2-5.)
- Step 2: Delete the files
git rm crates/zesdex-entities/src/seaorm/common/settings.rs
git rm crates/zesdex-entities/src/seaorm/common/app_config.rs
git rm crates/zesdex-entities/src/seaorm/common/memory.rs
git rm crates/zesdex-entities/src/seaorm/common/edit_log.rs
- Step 3: Remove their module declarations
In crates/zesdex-entities/src/seaorm/common/mod.rs, remove the corresponding pub mod settings;, pub mod app_config;, pub mod memory;, pub mod edit_log; lines (check first: cat crates/zesdex-entities/src/seaorm/common/mod.rs).
- Step 4: Build the whole workspace
Run: cargo build --workspace
Expected: no errors.
- Step 5: Run the full test suite and clippy
Run: cargo test --workspace && cargo clippy --workspace --all-targets -- -D warnings
Expected: all pass, no new warnings.
- Step 6: Commit
git add -A
git commit -m "chore: hapus entitas settings/app_config/memory/edit_log lama di zesdex-entities yang sudah digantikan zesdex-cms"