Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .trellis/spec/frontend/component-guidelines.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,45 @@ Questions to answer:

## Accessibility

### Progressive disclosure for troubleshooting controls

Settings pages must not present routine preferences, diagnostic evidence, and
host recovery as peer actions. Keep the common local action directly visible
and place support-only controls in one native `<details>` disclosure.

- The collapsed state exposes one common action plus one descriptive summary.
- Diagnostic preview/export remain inside the disclosure.
- Host check/recovery buttons render only when a workspace is authorized.
Before authorization, show the prerequisite as status text instead of
disabled controls.
- Do not show an idle host status such as "not checked". Announce only active
checking, ready, or error results after the user requests them.
- Preserve a light, unframed hierarchy. A divider and chevron are sufficient;
do not turn the advanced region into another card.
- Cover collapsed desktop/mobile geometry and both authorization branches in
tests.

Wrong:

```tsx
<Button>Reset UI</Button>
<Button>Preview diagnostics</Button>
<Button>Export diagnostics</Button>
<Button disabled={!workspace}>Check host</Button>
<Button disabled={!workspace}>Recover host</Button>
```

Correct:

```tsx
<Button>Reset UI</Button>
<details>
<summary>Diagnostics and recovery</summary>
<DiagnosticActions />
{workspace ? <HostActions /> : <HostAuthorizationRequirement />}
</details>
```

### Identity icons that reveal a panel action

When a dock header icon doubles as the affordance for collapsing that dock:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{"file": ".trellis/spec/frontend/quality-guidelines.md", "reason": "Review focused tests, lint, build, and dependency-neutral frontend changes."}
{"file": ".trellis/spec/frontend/component-guidelines.md", "reason": "Verify accessibility and stable settings control structure."}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{"file": ".trellis/spec/frontend/component-guidelines.md", "reason": "Preserve accessible disclosure and button conventions in the settings UI."}
{"file": ".trellis/spec/frontend/agent-control-safety.md", "reason": "Keep host recovery capability and authorization boundaries truthful while changing presentation."}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Simplify local recovery settings UX

## Goal

Reduce cognitive load in Settings > Updates & Support by progressively disclosing diagnostics and host recovery actions.

## Requirements

- Keep the common UI-reset action directly available in Settings > Updates & Support.
- Move diagnostic preview/export and host check/recovery behind one collapsed advanced disclosure.
- Do not render disabled host action buttons when no workspace is authorized; explain the prerequisite inside the disclosure instead.
- Keep all existing recovery capabilities, redaction behavior, and project-data safety guarantees unchanged.
- Preserve keyboard accessibility, status announcements, and localized copy.

## Acceptance Criteria

- [ ] The default recovery section presents one direct action and one advanced disclosure instead of five peer actions.
- [ ] Diagnostic preview and export remain available after expanding the disclosure.
- [ ] Authorized workspaces can still check and recover the host, with current progress/status feedback.
- [ ] Unauthorized workspaces see a concise prerequisite message and no disabled host action buttons.
- [ ] UI reset still clears only the existing interface preference keys and confirms success.
- [ ] Focused tests, localization extraction/compilation, lint, type checking, and the Agent contract validation pass.

## Notes

- This is a lightweight presentation and progressive-disclosure change; no recovery protocol or persistence contract changes are intended.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"id": "simplify-local-recovery-settings",
"name": "simplify-local-recovery-settings",
"title": "Simplify local recovery settings UX",
"description": "Reduce cognitive load in Settings > Updates & Support by progressively disclosing diagnostics and host recovery actions.",
"status": "completed",
"dev_type": null,
"scope": null,
"package": null,
"priority": "P1",
"creator": "tseka",
"assignee": "tseka",
"createdAt": "2026-07-24",
"completedAt": "2026-07-24",
"branch": null,
"base_branch": "fix/simplify-local-recovery-settings",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": null,
"relatedFiles": [],
"notes": "",
"meta": {}
}
5 changes: 3 additions & 2 deletions .trellis/workspace/tseka/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

<!-- @@@auto:current-status -->
- **Active File**: `journal-1.md`
- **Total Sessions**: 28
- **Total Sessions**: 29
- **Last Active**: 2026-07-24
<!-- @@@/auto:current-status -->

Expand All @@ -19,7 +19,7 @@
<!-- @@@auto:active-documents -->
| File | Lines | Status |
|------|-------|--------|
| `journal-1.md` | ~955 | Active |
| `journal-1.md` | ~988 | Active |
<!-- @@@/auto:active-documents -->

---
Expand All @@ -29,6 +29,7 @@
<!-- @@@auto:session-history -->
| # | Date | Title | Commits | Branch |
|---|------|-------|---------|--------|
| 29 | 2026-07-24 | Simplify troubleshooting recovery UX | `cb459e9` | `fix/simplify-local-recovery-settings` |
| 28 | 2026-07-24 | Release and install Cutout v0.1.7 | `13d4eda` | `chore/archive-release-install-v0-1-7` |
| 27 | 2026-07-24 | Fix duplicate preparation projection | `2f84034` | `fix/preparing-run-duplicate-projection` |
| 26 | 2026-07-23 | Release and install Cutout v0.1.6 | `ca28859b8a9a25e73fa9fb7c3cc703b4489f2091` | `chore/archive-release-install-v0-1-6` |
Expand Down
33 changes: 33 additions & 0 deletions .trellis/workspace/tseka/journal-1.md
Original file line number Diff line number Diff line change
Expand Up @@ -953,3 +953,36 @@ Published the single-active-surface preparation fix as the atomic four-platform
### Next Steps

- None - task complete


## Session 29: Simplify troubleshooting recovery UX

**Date**: 2026-07-24
**Task**: Simplify troubleshooting recovery UX
**Branch**: `fix/simplify-local-recovery-settings`

### Summary

Reduced Settings recovery cognitive load to one direct UI reset plus a lightweight diagnostics disclosure, hid unauthorized host actions, localized the new hierarchy, added component and desktop/mobile visual coverage, and captured the convention in frontend specs.

### Main Changes

- Detailed change bullets were not supplied; see the summary above.

### Git Commits

| Hash | Message |
|------|---------|
| `cb459e9` | (see git log) |

### Testing

- Validation was not recorded for this session.

### Status

[OK] **Completed**

### Next Steps

- None - task complete

This file was deleted.

88 changes: 88 additions & 0 deletions src/components/settings/sections/RecoverySection.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { createElement } from "react";
import { renderToStaticMarkup } from "react-dom/server";
import { setupI18n } from "@lingui/core";
import { I18nProvider } from "@lingui/react";
import { beforeEach, describe, expect, it, vi } from "vitest";

const workspace = vi.hoisted(() => ({
get: vi.fn<() => { handle: string; label?: string } | undefined>(),
}));

vi.mock("@/platform/authorized-workspace", () => ({
getAuthorizedWorkspace: workspace.get,
subscribeAuthorizedWorkspace: vi.fn(() => () => undefined),
}));

import { RecoverySection } from "./RecoverySection";

const i18n = setupI18n();
i18n.loadAndActivate({ locale: "en", messages: {} });

function renderRecoverySection() {
const html = renderToStaticMarkup(
createElement(
I18nProvider,
{ i18n },
createElement(RecoverySection),
),
);
const document = new DOMParser().parseFromString(html, "text/html");
const section = document.querySelector("section");
const advanced = section?.querySelector("details");
if (!section || !advanced) {
throw new Error("Recovery disclosure was not rendered");
}
return { section, advanced };
}

function buttonLabels(root: ParentNode) {
return [...root.querySelectorAll("button")].map((button) =>
button.textContent?.trim(),
);
}

describe("RecoverySection", () => {
beforeEach(() => workspace.get.mockReturnValue(undefined));

it("keeps one common action visible and progressively discloses advanced tools", () => {
const { section, advanced } = renderRecoverySection();
const directButtons = [...section.querySelectorAll("button")].filter(
(button) => !advanced.contains(button),
);

expect(directButtons.map((button) => button.textContent?.trim())).toEqual([
"Reset UI state",
]);
expect(section.textContent).toContain("Troubleshooting");
expect(advanced.open).toBe(false);
expect(advanced.querySelector("summary")?.textContent).toContain(
"Diagnostics and recovery",
);
expect(buttonLabels(advanced)).toEqual([
"Preview diagnostics",
"Export diagnostics",
]);
expect(advanced.textContent).toContain(
"Authorize a workspace before using host recovery.",
);
expect(section.textContent).toContain("Project data is not deleted.");
});

it("keeps host actions available after workspace authorization", () => {
workspace.get.mockReturnValue({
handle: "workspace.opaque",
label: "Authorized workspace",
});

const { advanced } = renderRecoverySection();

expect(buttonLabels(advanced)).toEqual([
"Preview diagnostics",
"Export diagnostics",
"Check host",
"Recover host",
]);
expect(advanced.querySelectorAll("button[disabled]")).toHaveLength(0);
expect(advanced.querySelector('[role="status"]')).toBeNull();
});
});
Loading