Skip to content

Move advanced Settings to a TOML-backed config with separate docs #3

Description

@jonmagic

Problem

Voice command templates, inactive-line combiner command templates, and local cleanup retention are now split across CLI settings and text-heavy Settings panels. That is hard to maintain, awkward in the GUI, and not ideal for the likely configuration path: a user asking an agent to update local TSRS settings. TSRS should make the advanced configuration file-first while preserving the Settings UI for visibility, validation, and safe edits.

Goals

  • Introduce one TOML config file as the source of truth for advanced settings: app-owned voice synthesis command, inactive-line combiner command, variable substitution, and local cleanup retention.
  • Preserve developer-level customization: users must be able to define the actual voice synthesis command and combiner command rather than picking only built-in options.
  • Preserve the existing shell-free command-template safety model. Do not switch to ${...} shell-looking placeholders; continue from the current <text-file>, <output-file>, <voice-id>, <app-bin>, <input>, and <system> style unless a migration explicitly preserves compatibility.
  • Provide an explicit upgrade path for settings from the released 1.1.2 SQLite store before TOML becomes the source of truth. Do not lose existing combiner, speech, first-start, shortcut, voice-selection, mode, mute, active-line, or queue-related state.
  • Keep docs separate from the config file. The TOML can be concise; long explanations belong in docs/ and any generated public-site docs source if this repo owns one.
  • Show configured values in Settings and allow editing from Settings through the same parser/validator used by direct TOML edits.
  • Keep playback safe: invalid config fails quiet, surfaces errors, and must not speak unexpectedly.

Proposed config shape

Use a sectioned TOML file that preserves existing placeholder semantics, for example:

[voice]
command = "/usr/bin/say -f <text-file> -o <output-file> --voice <voice-id>"

[voice.variables]
voice-id = "Samantha"

[combiner]
command = "llm prompt <input> --system <system> --no-stream --no-log"

[combiner.variables]
style = "brief"

[retention]
cleanup_retention_minutes = 525600

Placeholder syntax should be explicit, allowlisted, and substituted as argv-safe values, not shell-expanded strings. Unknown placeholders should fail validation. If file-backed placeholders such as <input-file> or <system-file> are added later, keep <input> and <system> compatibility or provide a clear migration path.

Proposed approach

  1. Define typed config structs and parser/validator behavior in RelayCore.swift or a dedicated shared core file.
  2. Add CLI commands such as relay config path, relay config validate, relay config reload, and relay config edit.
  3. Migrate existing SQLite-backed advanced settings into the TOML config without losing existing user values.
  4. Treat the released 1.1.2 store as the baseline upgrade fixture. Read settings from the existing SQLite database, create the TOML config only if it is missing, and do not overwrite a user-edited TOML file without an explicit command. Map inactive_line_combiner_command into [combiner].command, explicitly decide whether compatible speech_command values can become [voice].command, and leave runtime/user-state keys such as mode, muted, active_line, first_start_setup_complete, command_palette_shortcut, speech_voice_identifier, last_spoken_line, and live-batch bookkeeping in SQLite.
  5. Include a second migration fixture for current post-1.1.2 dogfood settings that may already contain voice_command, voice_command_last_error, or cleanup_retention_minutes, mapping only stable config values into TOML and preserving diagnostic/runtime state in SQLite.
  6. Explicitly handle the legacy speech-command setting. Either migrate compatible values into the app-owned voice.command model or deprecate it with clear CLI output and docs; do not reintroduce CLI-owned speech.
  7. Update Settings so it reads the same config model, displays effective values, offers Open Config / Reveal Config / Validate / Reload, and edits safe fields through the shared validator.
  8. Decide whether Settings saves normalize the TOML file and document that behavior explicitly. Do not promise preservation of arbitrary user comments if Settings rewrites the file.
  9. Move mature explanations into docs/user-guide.md, docs/inactive-line-combination.md, docs/development.md, and any public-site generated docs source if present. If jonmagic.com docs are not generated from this repo, note the external follow-up instead of adding a fake path.
  10. Keep direct-profile command execution shell-free, bounded, and app-owned. Voice commands may write audio files for the app to play; combiner commands may return validated JSON output; neither may speak directly.

Acceptance criteria

  • A single TOML config covers app-owned voice command, inactive-line combiner command, variable substitution values, and cleanup retention.
  • Existing combiner, settings --voice-command, and cleanup-retention workflows continue to work or emit clear migration guidance.
  • The legacy speech-command path has an explicit migration/deprecation decision with tests.
  • Direct file edits and Settings edits use one parser/validator and produce the same effective config.
  • Existing placeholders keep working, and any new placeholder syntax has compatibility tests.
  • Unknown placeholders, invalid commands, invalid retention values, and malformed TOML fail validation with actionable errors.
  • Invalid config leaves TSRS quiet and does not enable playback.
  • Settings shows the effective configured values and the current config error state.
  • Documentation is updated in repo docs, and any jonmagic.com docs source is updated only if this repo actually owns it.
  • Tests cover parser defaults, valid examples, invalid placeholders, malformed TOML, migration from an actual 1.1.2-style settings table, migration from current pre-TOML dogfood settings, Settings visibility, and CLI config commands.

Out of scope

  • Removing app-owned playback.
  • Letting the CLI speak directly.
  • Cloud-provider-specific voice integration in core TSRS.
  • Full GUI preservation of arbitrary TOML comments.
  • App Store-safe profile expansion; App Store notes remain legacy hardening references unless that product direction is reopened.

Activity

  1. jonmagic commented on Jul 4, 2026

    @jonmagic
    OwnerAuthor

    First execution milestone landed in 7b77258.

    What changed:

    • Added dependency-free TOML config parsing for the known TSRS sections.
    • Added relay config path, relay config show, relay config validate, and relay config reload.
    • relay config show now gives a 1.1.2 SQLite-settings upgrade preview when no config file exists, without writing a new persistence file or switching runtime playback to TOML.
    • Added tests for config commands, invalid placeholders, 1.1.2-style settings preview, and preserving an existing config file.
    • Added AGENTS.md guidance plus .github/skills/settings-ui-verification for the issue Add accessibility-backed Settings UI smoke tests and screenshot capture #2 Accessibility/screenshot workflow.

    Important remaining checkpoint: before TSRS reads voice/combiner/retention from TOML at runtime, we still need the explicit fail-quiet contract for malformed/invalid config so the app surfaces errors without unexpectedly speaking or falling back to a command path.

  2. jonmagic commented on Jul 4, 2026

    @jonmagic
    OwnerAuthor

    Added the release-upgrade gate in 0a1bd5e: scripts/test-112-upgrade.sh.

    What it covers now:

    • Builds the tagged v1.1.2 CLI in a temporary git worktree.
    • Uses that real 1.1.2 CLI to create a SQLite database with queue data, active line, live mode, mute, first-start completion, speech command, and combiner command.
    • Runs the current bundled relay against the same database.
    • Verifies relay config show and relay config validate produce the TOML upgrade preview, preserve runtime state, keep the queued relay, and do not create config.toml before the write migration exists.

    Remaining for 2.0.0: once the TOML write/source-of-truth migration is implemented, extend this same harness to assert the file is created exactly once, existing TOML is not overwritten, invalid TOML fails quiet, and runtime voice/combiner/retention reads come from TOML safely.

  3. jonmagic commented on Jul 4, 2026

    @jonmagic
    OwnerAuthor

    TOML source-of-truth migration landed in 4b91ff8.

    What changed:

    • config.toml is created once from existing SQLite settings and preserved if already user-edited.
    • TOML is now the source of truth for advanced voice command, inactive-line combiner command, and cleanup retention.
    • SQLite remains the source for runtime/user state: mode, mute, active line, queue, first-start completion, shortcut, voice selection, diagnostics, and usage.
    • Invalid TOML now fails quiet: status/settings expose configError, Settings Voice diagnostics show the config error, playback reports blocked/muted, and app-claim-next returns null so queued relays are not spoken.
    • scripts/test-112-upgrade.sh now covers real v1.1.2 database upgrade, one-time TOML creation, existing-TOML preservation, invalid-config fail-quiet behavior, and queue/runtime state preservation.

    Validated with focused XCTest, full XCTest, direct build, app restart, Settings screenshot/interaction smoke capture, and the v1.1.2 upgrade harness.

  4. jonmagic commented on Jul 4, 2026

    @jonmagic
    OwnerAuthor

    Settings UX and persistence smoke milestone landed in deba948.

    What changed:

    • Redesigned the Voice Settings panel around the TOML config file with Open Config, Reveal in Finder, Validate, and Reload controls.
    • Added explicit Save buttons for voice command and inactive-line combiner command instead of relying only on save-on-close.
    • Kept the compact Advanced and Combiner panels visible in screenshots.
    • Added an app-owned relay debug settings-roundtrip path for UI smoke testing. The screenshot workflow can now modify Voice, Combiner, and cleanup retention through the real Settings controller, verify the bundled CLI sees the modified values, restore the original TOML, and verify restore.
    • Updated docs/AGENTS with the TSRS_SETTINGS_UI_ROUNDTRIP=1 TSRS_SETTINGS_UI_REQUIRE_INTERACTIONS=1 scripts/capture-settings-ui.sh release gate.

    Validated with focused XCTest, full XCTest, direct build/restart, v1.1.2 upgrade harness, and strict Settings capture/roundtrip artifacts in .artifacts/settings-ui/settings-ux-roundtrip/.

  5. jonmagic commented on Jul 6, 2026

    @jonmagic
    OwnerAuthor

    Final closeout: all requirements in this issue have been satisfied on main.

    What is now covered:

    • config.toml is the source of truth for advanced voice command, inactive-line combiner command, variable values when present, and cleanup retention.
    • Existing 1.1.2 SQLite settings upgrade into TOML once, while queue/runtime state remains in SQLite.
    • Invalid TOML/config fails quiet and leaves queued relays unclaimed for speech.
    • Settings uses the same config model and provides Open, Reveal, Validate, Reload, and explicit Save controls.
    • The duplicate advanced CLI surfaces were consolidated under relay config set, with existing compatibility/errors covered.
    • Docs and release-upgrade/UI verification gates have been updated.

    Follow-up refinements after the main milestone also landed: generated config no longer emits empty variable tables, and Provider line voices stay TOML-only instead of stretching the Voice Settings panel.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    triage/ai-reviewedReviewed by the cautious issue triage workflow.triage/human-neededHuman maintainer review is needed.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions