Skip to content

desktop: persist the desktop configuration as name-keyed KDL - #160

Merged
pragmatrix merged 41 commits into
masterfrom
persisting-desktop-configuration
Sep 30, 2026
Merged

pragmatrix merged 41 commits into
masterfrom
persisting-desktop-configuration

Conversation

@pragmatrix

@pragmatrix pragmatrix commented Sep 29, 2026 •

Copy link
Copy Markdown
Owner

Why

The desktop configuration (projects, launchers, their matrix placements, and the
startup profile) is the only desktop state that persists across sessions, but it
was stored as desktop.json, regenerated wholesale, and shaped for the machine:
no comments, camelCase keys, and a tags field no consumer read. The file is
human-maintained — the intended workflow is that a user edits it by hand — so it
should speak in the same vocabulary the desktop presents, and survive automated
writes without shredding a user's comments and blank lines.

Summary

  • desktop.kdl, keyed by name. Replaces desktop.json (and its
    ConfigFile/ProjectConfiguration::from_dir/from_json path) with
    desktop/src/projects/persistence.rs: a parsed KdlDocument kept in memory for
    the session, loaded from / written to <projects dir>/desktop.kdl.
  • Surgical edits. Each configuration change is mirrored as a node-level edit,
    so user comments and formatting survive byte-identical. A spike confirmed kdl
    nodes carry their comments across remove/insert moves.
  • Name-keyed identity, duplicates allowed. Launcher ids are fresh UUIDs per
    session, so the file references names; per ADR 0010 names may repeat (the
    planner keeps the "at least one launcher" invariant), and a name lookup
    resolves to the nearest node. Node identity lives in a re-parseable tag held
    in KdlNode::span (NodeTags), so live entries map to file nodes without
    renaming the user's text. ConfigKeys is gone.
  • Comments preserved in both directions. Appending a project or launcher
    node only copies whitespace-only spacing from its sibling (a comment rides on
    the preceding node's leading and belongs to it), and replacing the startup
    launcher keeps the comments above it. Both pinned by tests.
  • One write per transaction. Changes land in the in-memory document
    immediately and the file is written once, synchronously and atomically
    (temp-file + rename), at the end of transact — so actions like
    remove-launcher, whose shift moves several launchers, persist as a single
    write. Setup transactions (which replay the file's own configuration)
    neither mirror nor write.
  • Not transactional yet — documented. transact states that a failing
    change leaves earlier effects applied (state may be inconsistent, including
    the document mirror, which lands before the apply), with the call site
    cross-referenced.
  • One owner per invariant. ProjectChange::RemoveSlot is gone; the matrix
    computes the slot-removal shift and the plan emits explicit MoveLauncher
    changes, so the file mirrors the same moves the model made. tags is removed
    from the configuration types.
  • TransactionEffectsMode threaded into apply_change /
    apply_project_change / the mirroring call, so a change states in which terms
    it is applied (setup replays the file without writing it back).
  • Documentation: ADRs 0006 and 0010 record the decisions; CONTEXT.md
    defines the domain terms; shared-instructions.md records the resulting
    convention (surgical edits for human-maintained config files) and combines the
    function-ordering rule into one bullet.

Validation

  • cargo check -p massive-desktop clean; cargo clippy -p massive-desktop
    clean; cargo fmt --check clean.
  • cargo test -p massive-desktop: 52 passed, 0 failed.

Note

This was created entirely with AI assistance.

- Merge ConfigKeys into ConfigurationDocument; the id-to-name map and the
  document change together.
- Persist the default configuration at load time so the file exists before
  the first change; drop the Option path.
- Leave the change pending when a write fails, so the next flush retries.
- Extract the KDL<->data conversation into kdl_codec, rename the node
  lookup helpers *_mut, order helpers by stepdown.
- Drop pub(crate) items inside the persistence module; mod.rs only declares
  child modules and re-exports the facade type.
- Remove the too-specific shared-instructions paragraph.
@pragmatrix

Copy link
Copy Markdown
Owner Author

Addressed all review threads:

  • Persistence module restructured: persistence.rs is now projects/persistence/ — mod.rs (declarations + facade re-export), configuration.rs (ConfigurationDocument, ConfigKeys), and kdl_codec.rs (the KDL↔configuration-data conversation).
  • ConfigKeys merged into ConfigurationDocument: the id→name map and the document now change together; the separate constructor parameter and Option<Option<&str>> startup-profile parameter are gone.
  • Startup-profile translation now resolves names inside the document (including the cleared-profile case, which removes the startup node).
  • No Option path: load requires a projects directory (desktop startup fails fast if none can be resolved) and writes the default configuration when the file is missing, so the file exists before the first change is persisted.
  • Flush error handling: a failed write logs and leaves the change pending, so the next flush retries it.
  • All pub(crate) items inside the persistence module are either private or public (pub facade, used across the crate); the ambiguous ones are gone.
  • Renamed project_node → project_node_mut, project_children → project_children_mut (mutating accessors), and use std::collections::HashMap imports.

All threads resolved; no replies posted.

Comment thread desktop/src/desktop_system/command_dispatch.rs Outdated
Comment thread desktop/src/projects/persistence.rs Outdated
Comment thread desktop/src/projects/persistence.rs Outdated
Comment thread desktop/src/projects/persistence.rs Outdated
Comment thread desktop/src/projects/persistence.rs Outdated
Comment thread desktop/src/projects/persistence.rs Outdated
Comment thread .github/shared-instructions.md Outdated
Comment thread desktop/src/desktop_system.rs
Comment thread desktop/src/desktop_system/change.rs
Comment thread desktop/src/projects/persistence/kdl_codec.rs Outdated
The no-file policy (writing the built-in default) moves up the call chain
to the Desktop startup sequence: load now fails on any file error, and
initialize_file is the explicit, separately invokable fallback. Also
import the kdl_codec helpers instead of spelling super::kdl_codec at each
call site.
Load derives the live ProjectSet from the parsed file and registers the
resulting names in one step, returning both the document and the set;
the separate register_loaded step is gone.
The spawn-parameter (JSON) <-> KDL node conversation moves to its own
parameter module; the KDL document's structure (change application,
configuration read-back) stays in the document module, now named for
what it holds instead of the codec framing.
@pragmatrix

Copy link
Copy Markdown
Owner Author

Parameter (JSON) conversions separated and module renamed:

  • kdl_codec.rs is now document.rs: only the KDL document's structure — default config, atomic write, change application as node edits, configuration read-back.
  • The JSON spawn-parameter conversation (params_node, params_value, json_value, number_value) moved to its own parameters.rs — InstanceParameters is serde_json::Map<String, Value>, so the JSON types are the parameter domain's own types, not KDL codec residue.

Also replaces the inline std::fs::read_to_string with a fs import.
Comment thread desktop/src/projects/persistence/configuration.rs Outdated
Comment thread desktop/src/projects/persistence/configuration.rs Outdated
Comment thread desktop/src/desktop.rs Outdated
Comment thread desktop/src/projects/persistence/configuration.rs Outdated
Comment thread desktop/src/projects/persistence/configuration.rs Outdated
Comment thread desktop/src/projects/persistence/configuration.rs Outdated
Comment thread desktop/src/projects/persistence/configuration.rs Outdated
Comment thread desktop/src/desktop_system.rs Outdated
Comment thread desktop/src/desktop.rs Outdated
@pragmatrix
pragmatrix force-pushed the persisting-desktop-configuration branch from a736182 to 84bea09 Compare September 30, 2026 07:16
@pragmatrix
pragmatrix merged commit e570c0e into master Sep 30, 2026
2 checks passed
@pragmatrix
pragmatrix deleted the persisting-desktop-configuration branch September 30, 2026 07:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant