Skip to content

Profiles: rename, naming, ordering, deletion, save/cancel UX - #31

Open
mwthomson wants to merge 13 commits into
mainfrom
profiles-rework
Open

Profiles: rename, naming, ordering, deletion, save/cancel UX#31
mwthomson wants to merge 13 commits into
mainfrom
profiles-rework

Conversation

@mwthomson

Copy link
Copy Markdown
Owner

Summary

Renames DireUI's "Configurations" concept to "Profiles" throughout the codebase and adds the features scoped in #29 (closed):

  • Every Profile gets a required, user-editable, non-unique name (previously just a bare remembered path), with inline rename via a pencil icon.
  • The active Profile is always the first row; the rest are ordered by Last Activated, most recent first.
  • An "make this the active profile" checkbox on the Add-profile form (unchecked by default).
  • Profile deletion via a trashcan icon opening a shared dialog: Remove (DireUI record only) vs. a second-confirmed Delete (also removes the Config File from disk). Deleting the active Profile is allowed and auto-promotes another Profile to active.
  • Save feedback on both editor pages (changes saved / no changes to save / save failed: [reason]), with no-change detection that skips the write entirely, and a Cancel button that always discards silently.
  • state.json's schema change (name + Last Activated timestamp) is an intentional breaking change with no migration, per new ADR-0005.

Domain vocabulary updated in CONTEXT.md (Profile, Active Config, Last Activated, Remove, Delete) to match.

Built via subagent-driven-development: 9 plan tasks (docs/superpowers/plans/2026-08-30-profiles-rework.md), each with its own implementer + task-scoped reviewer pass (two tasks needed one fix-and-re-review round each for real bugs the reviewers caught), followed by a final whole-branch review that found one Critical issue (an unvalidated delete path — arbitrary file deletion given this server has no auth/CSRF) and four Important cross-task issues, all fixed and independently re-verified. Full audit trail in .superpowers/sdd/progress.md.

One non-blocking follow-up was filed separately as #30 (state.json's silent reset on an unreadable file could use a warning + backup-before-overwrite) rather than folded into this branch.

Test plan

  • cargo build — zero warnings
  • cargo test — 135/135 passing
  • Full manual browser walkthrough against a real server + filesystem: first-run, add/rename/switch-active, ordering, raw-config save (change/no-change/cancel), directives save (validation error/no-change/cancel), Remove, Delete with auto-promote, Delete permission-denied path
  • Final whole-branch review (opus) — Critical + Important findings fixed and re-verified clean

🤖 Generated with Claude Code

mwthomson and others added 13 commits August 30, 2026 22:19
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Carries changes-saved / no-changes / removed / deleted / delete-failed
messages across a redirect to the Profiles page via a `flash` query
parameter, since the app has no session or cookie layer. Not wired into any
handler yet.
Profiles now have a required display name and a Last Activated time. The
Profiles list always shows the active Profile first, then the rest ordered
by Last Activated (most recent first). Adding a Profile can optionally make
it active immediately via a new checkbox on the Add form.

Refs #29
A pre-Profile-rename state.json now has no `profiles` key, so deserialization
fails and DireUI falls back to AppState::default() rather than attempting to
interpret the old known_configs shape — this is the intended, documented
behavior (ADR-0005), not a bug; this test pins it down explicitly.
Save now compares the submitted text against the Active Config's current
on-disk content before writing: identical content redirects to Profiles with
"no changes to save" instead of rewriting the file; a real change redirects
with "changes saved"; a write failure re-renders the editor in place with
"save failed: [reason]" and the user's unsaved edits intact. This also fixes
raw-config write failures being silently swallowed (only eprintln'd) before
this change. Cancel always discards and returns to Profiles.
… edit

Task-review fix for two Important findings against commit 1c996c3, not
new functionality:

- save_raw_config compared the CRLF-normalized submitted textarea content
  against current_content read straight from disk, which is not
  normalized. A Config File with CRLF line endings therefore always
  looked "changed" on an unedited Save, silently rewriting the file's
  line endings every time. Normalize current_content the same way before
  the comparison. Adds a regression test
  (save_raw_config_detects_no_change_against_crlf_file_on_disk) that
  writes a CRLF file, submits it unchanged, and asserts both the
  no-change flash and that the on-disk bytes are untouched.

- save_directives's write_config(...) call was changed to
  `let _ = write_config(...)` plus a comment, purely to silence the new
  #[must_use] warning after write_config started returning
  Result<(), String>. That function is out of scope for this task and is
  rewritten wholesale by a later task, so revert the call site back to a
  bare statement. This leaves an expected unused_must_use warning at that
  line until the later task replaces the function body.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Compares the built Document against the on-disk parsed Document (structural
equality, so this stays compatible with ADR-0001's round-trip preservation)
before writing. Validation failures keep their existing per-field messages
and now also show a top-level "save failed" message. Cancel always discards
and returns to Profiles.
Adds CONTEXT.md entries for Last Activated, Remove, and Delete, and
sharpens the Active Config/Profile entries to describe what's actually
being built rather than the prior aspirational phrasing. Also records
ADR-0005 (no state.json migration) and the implementation plan this
branch executes.
…nt equality

set_curated unconditionally rewrites a matched directive line's raw text to
canonical single-space form on every save, for every field in
CURATED_FIELDS, regardless of whether the value actually changed. This made
the doc == original_doc check in save_directives unreliable: a config file
with any curated directive line in non-canonical spacing (plausible in a
hand-typed direwolf.conf) would get its spacing silently reformatted and
incorrectly flash "changes saved" on a save where the user changed nothing.

Fix by tracking, per field, whether the submitted value differs from
original_doc's parsed value (via get_curated, before any mutation), and
using that to decide NoChange vs. Saved instead of Document equality.
Validation still runs for every field as before.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A pencil icon next to each Profile's name toggles it into an editable field
(pure client-side toggle, no server round-trip for the toggle itself); saving
posts to the new /profiles/rename route and returns to the Profiles page.
…e file)

A trashcan icon opens a shared dialog with three choices: cancel, Remove
(deletes the Profile record only), or a second-confirmed Delete (also
removes the Config File from disk, irreversible). Deleting the active
Profile is allowed — DireUI auto-promotes the next Profile by Last Activated
so there's never a Profile list with nothing active.
.profile-rename-form{display:flex} and the browser's [hidden]{display:none}
have equal specificity, so the class rule (later in the cascade) was
winning and the rename form rendered open on every page load instead of
staying hidden until the pencil icon is clicked. Found during Task 9's
manual browser walkthrough — no automated test could catch this, since it's
purely a CSS cascade/rendering behavior. A more specific
.profile-rename-form[hidden] rule restores the intended hidden state.
…mpty list, name fallback, curated-line reformatting, stale terminology

Addresses the final whole-branch review of profiles-rework before merge:

- delete_profile now checks the submitted path is a known Profile before
  touching the filesystem, closing an unauthenticated arbitrary-file-deletion
  gap (this server has no auth/CSRF protection). Unknown paths redirect with
  flash=delete_failed and leave disk and state untouched.
- views::first_run now accepts and renders a flash banner (via a shared
  flash_banner_html helper also used by profiles_page), so ?flash=removed /
  ?flash=deleted is no longer silently dropped when the Profile list becomes
  empty.
- state::normalize_profile_name falls back to the Config File's basename when
  a Profile name is empty/whitespace after trimming, applied in both
  add_profile and rename_profile, enforcing the "names are required" rule
  server-side rather than relying solely on the HTML `required` attribute.
- save_directives now only calls set_curated (which unconditionally
  reformats a line to canonical spacing) for fields whose submitted value
  actually differs from the on-disk value, so a real edit to one curated
  field no longer silently reformats unrelated curated lines. Untouched
  fields are no longer re-validated on that save (accepted trade-off).
- Cleaned up stale "config manager"/"saved config"/".config-badge" wording
  left over from the Profile rename, and adjusted the delete-icon
  aria-label ("Remove or delete profile") since Remove, not Delete, is the
  dialog's non-destructive default action.

Adds handler-level tests for delete_profile (unknown path rejected, known
path deleted, active-Profile-deletion promotion through the route handler),
a first_run flash-rendering test, state-level name-fallback/trim tests, and
a save_directives test proving an unrelated curated line's non-canonical
spacing survives a genuine single-field change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

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