Skip to content

Commit 5868d35

Browse files
Merge pull request #7 from AlbertArakelyan/6-round-3---collections-workspaces-sidebar-tree-tabs-file-persistence-v2
6 round 3 collections workspaces sidebar tree tabs file persistence v2
2 parents c3ad1d0 + 89d90b3 commit 5868d35

30 files changed

Lines changed: 2853 additions & 415 deletions

.claude/agents/senior-rust-engineer.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,26 @@ model: sonnet
77

88
You are a senior Rust engineer with deep expertise in TUI development using ratatui and async programming with tokio. You are building **forge** — a terminal-native API client (Postman in the terminal). Read `SPEC.md` for the full spec and `CLAUDE.md` for project conventions before writing code.
99

10+
---
11+
12+
## Pre-Implementation Reasoning (MANDATORY)
13+
14+
**Before writing any code**, run the reasoning skill checklist at `.claude/skills/reasoning/SKILL.md`.
15+
16+
Work through every section that applies to your task:
17+
18+
1. **Data Model** — new/changed structs, enum variants, serde defaults, exhaustive matches
19+
2. **Lifecycle/Sync** — when created, modified, flushed, destroyed, loaded on restart
20+
3. **Idempotency/Dedup** — what if triggered twice? search before push
21+
4. **Inverse Operations** — every open/create needs a matching close/delete/save
22+
5. **UI Completeness** — focus cycle, keybind hints, empty state, overflow/scroll
23+
6. **State Coherence** — clamp indices after mutations, invalidate caches
24+
7. **"Who Else Touches This?" Audit** — grep every symbol you change; categorize by create/read/update/delete/persist/display
25+
26+
Answer each relevant question (one-liner is enough) before touching a single source file. Unanswered questions = gaps that will become bugs.
27+
28+
---
29+
1030
## Core Principles
1131

1232
- **Correctness first**: safe Rust, no `unwrap()` in production paths — use `?` and proper error types

.claude/settings.local.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66
"Bash(echo No tokio-util in lock yet:*)",
77
"WebFetch(domain:docs.rs)",
88
"Bash(cd \"C:\\\\Users\\\\alber\\\\Desktop\\\\forge\" && cargo check 2>&1)",
9-
"Bash(ls:*)"
9+
"Bash(ls:*)",
10+
"Bash(xargs grep:*)",
11+
"Bash(cargo check:*)",
12+
"Bash(cargo test:*)"
1013
]
1114
},
1215
"enableAllProjectMcpServers": true,

.claude/skills/reasoning/SKILL.md

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
---
2+
name: reasoning
3+
description: >
4+
Self-questioning checklist to run before implementing any feature.
5+
Use to surface edge cases, lifecycle gaps, persistence requirements,
6+
dedup needs, and UI completeness issues before writing code.
7+
Invoke mentally (or literally write answers) before starting implementation.
8+
---
9+
10+
# Feature Reasoning Checklist
11+
12+
Before writing code, work through these question groups out loud.
13+
Answer each one briefly — a one-liner is enough. Unanswered questions = gaps in the plan.
14+
15+
---
16+
17+
## 1. Data Model Questions
18+
19+
*Trigger: adding or changing any struct field, enum variant, or type.*
20+
21+
- Does this new field need to be **persisted** (saved to disk / TOML)?
22+
→ If yes: add `#[serde(default)]` so old files still load.
23+
- Are there **struct literal initializations** (`Struct { field: val, ... }`) elsewhere
24+
that will now fail to compile?
25+
→ Search for every place the struct is constructed by name.
26+
- Does the `Default` impl need updating?
27+
- Do any `Clone`, `PartialEq`, or `Display` impls need to handle the new field?
28+
- If I added an enum variant: are there `match` statements on this enum elsewhere?
29+
→ Every exhaustive match must get a new arm.
30+
31+
---
32+
33+
## 2. Lifecycle / Sync Questions
34+
35+
*Trigger: any feature that creates, modifies, or destroys stateful data.*
36+
37+
- **When is this data created?** (startup, user action, event?)
38+
- **When is it modified?** (every keystroke, on submit, on navigate-away?)
39+
- **When should it be flushed/saved?** Never assume "it's saved automatically."
40+
→ List every code path that changes it; each path needs a save call or a sync point.
41+
- **When is it destroyed?** (tab close, workspace switch, app exit?)
42+
→ Every destruction path must flush state first.
43+
- What happens on **app restart**? Is the state loaded back correctly?
44+
→ Test: create → close app → reopen → is state intact?
45+
46+
---
47+
48+
## 3. Idempotency / Dedup Questions
49+
50+
*Trigger: any feature that opens, creates, or adds something.*
51+
52+
- What happens if the user **triggers this action twice** in a row?
53+
→ Should it be a no-op, an error, or create a second item?
54+
- Is there an **existing open instance** that should be re-focused instead?
55+
→ Search open_tabs / active list before pushing a new item.
56+
- What if the **item already exists** with the same identity?
57+
→ Define identity: is it by ID, by name, by position?
58+
59+
---
60+
61+
## 4. Inverse / Symmetric Operations
62+
63+
*Trigger: any "open", "create", "start", or "enable" action.*
64+
65+
For every action X, list its inverses and verify each handles the new state:
66+
67+
| Action added | Inverses to check |
68+
|---|---|
69+
| open tab | close tab, switch tab, switch workspace, app exit |
70+
| create item | delete item, rename item, duplicate item |
71+
| add field | all constructors, all serialization round-trips |
72+
| enable feature | disable feature, reset to default |
73+
74+
**Every entry point needs a matching exit point that cleans up / persists.**
75+
76+
---
77+
78+
## 5. UI Completeness Questions
79+
80+
*Trigger: any new visible element (widget, panel, row, popup, indicator).*
81+
82+
- Should this element be **keyboard-navigable**?
83+
→ If yes: add a `Focus` variant, update `next()`/`prev()`, add visual indicator.
84+
- Does it need a **direct shortcut key** (number, letter)?
85+
- Does it need **keybinding hints** in a status bar or hint footer?
86+
- What happens when the element is **empty** (zero items, blank state)?
87+
→ Render a placeholder; don't panic on `list[0]`.
88+
- What happens when the element is **out of screen bounds** (many items, small terminal)?
89+
→ Clamp scroll_offset; ensure cursor stays visible.
90+
91+
---
92+
93+
## 6. Related State Coherence
94+
95+
*Trigger: any action that adds or removes items from a list, or changes active indices.*
96+
97+
- After this action, are all **indices still valid**?
98+
→ active_tab_idx, cursor, scroll_offset — clamp them after mutations.
99+
- Are there **other state fields** that reference the mutated data by index or ID?
100+
→ Update or invalidate caches (e.g. highlighted_body, selected row).
101+
- Does any **other component render** based on the data I changed?
102+
→ Read render functions that touch the same state; ensure they handle new shape.
103+
104+
---
105+
106+
## 7. The "Who Else Touches This?" Audit
107+
108+
*Run this for every struct, field, or function you modify.*
109+
110+
1. Search the codebase for all uses of the symbol.
111+
2. Categorize: create / read / update / delete / display / persist / test.
112+
3. For each category: does my change break or require updating that site?
113+
114+
```
115+
Symbol: CollectionRequest
116+
create: CollectionRequest::new(), struct literal in sidebar_duplicate → needs url/body_raw
117+
read: flatten_tree(), find_col_request_by_id() → fine, reads by field
118+
update: update_col_request_state() → needs url/body_raw
119+
persist: save_collection_meta() → handled by serde
120+
open: handle_sidebar_enter() → needs to load url/body_raw back
121+
close: close_active_tab() → needs to sync url/body_raw first
122+
```
123+
124+
---
125+
126+
## Checklist (run before every implementation)
127+
128+
- [ ] Listed all struct literals for modified structs → all compile?
129+
- [ ] Named every lifecycle stage (create / modify / destroy) and wired save/load
130+
- [ ] Checked for dedup: what if this is triggered twice?
131+
- [ ] Verified all inverses (close, delete, switch) handle the new state
132+
- [ ] If new UI element: added to Focus cycle, visual indicator, hint bar
133+
- [ ] Indices/cursors clamped after any list mutation
134+
- [ ] "Who else touches this?" audit complete — no missed call sites

PROGRESS.md

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
- [x] Round 1 — Core Request Engine
66
- [x] Round 2 — Environment Variables (partly - [more details here](SPEC.md#implementation-tasks-1))
7-
- [ ] Round 3 — Collections & Workspaces
7+
- [x] Round 3 — Collections & Workspaces
88
- [ ] Round 4 — Authentication
99
- [ ] Round 5 — Request Headers & Query Params (Done Partly)
1010
- [ ] Round 6 — Request Body Editor (Done Partly)
@@ -124,6 +124,51 @@
124124

125125
---
126126

127+
## Round 3 — Collections & Workspaces ✓
128+
129+
### Files Implemented (13 new/major files)
130+
131+
| Layer | Files |
132+
|---|---|
133+
| State | `state/collection.rs`, `state/workspace.rs`, `state/app_state.rs` (major migration) |
134+
| Storage | `storage/workspace.rs`, `storage/collection.rs` |
135+
| UI | `ui/sidebar.rs` (full rewrite), `ui/request_tabs.rs`, `ui/naming_popup.rs`, `ui/confirm_delete.rs`, `ui/workspace_switcher.rs` |
136+
| App Logic | `app.rs` (sidebar CRUD, tab management, workspace switching) |
137+
138+
### Architecture
139+
140+
- **AppState migration**: `state.request`/`state.response``state.workspace.open_tabs[active_tab_idx]`; accessed via `state.active_tab()` / `state.active_tab_mut()`
141+
- **Environments moved**: `state.environments``state.workspace.environments`; `state.active_env_idx``state.workspace.active_environment_idx`
142+
- **Storage path**: `%APPDATA%/forge/workspaces/<ws-name>/` (Windows) / XDG / macOS equivalents
143+
- **Sidebar tree**: `SidebarNode` enum flattened to a list via `flatten_collections()`; collapsed node IDs tracked in `sidebar.collapsed_ids: HashSet<Uuid>`
144+
- **Tabs**: `WorkspaceState.open_tabs: Vec<RequestTab>`; `active_tab_idx` tracks focus; tabs persist to `workspace.toml`
145+
146+
### Keybindings Added
147+
148+
| Key | Action |
149+
|---|---|
150+
| `Ctrl+W` | Workspace switcher popup |
151+
| `Ctrl+n` (Sidebar) | New collection |
152+
| `n` (Sidebar) | New request |
153+
| `f` (Sidebar) | New folder |
154+
| `r` (Sidebar) | Rename selected item |
155+
| `d` (Sidebar) | Delete selected item |
156+
| `D` (Sidebar) | Duplicate selected item |
157+
| `h` / `l` (Sidebar) | Collapse / expand node |
158+
| `/` (Sidebar) | Toggle search mode |
159+
| `Alt+1–9` | Switch to tab N |
160+
| `Alt+w` | Close active tab |
161+
| `[` / `]` | Cycle open tabs (non-UrlBar focus) |
162+
163+
### Gotchas & Fixes
164+
165+
- Sidebar search runs inline at the footer row (repurposed hint row); `NamingState` carries the HTTP method for new requests so method persists through the naming popup flow
166+
- `active_tab()` returns `Option<&RequestTab>` — all render functions must handle `None` gracefully
167+
- Workspace save triggered on every tab/request mutation via `dirty` flag; debounced via the Tick event
168+
- `flatten_collections()` recurses into folders and respects `collapsed_ids` to hide children
169+
170+
---
171+
127172
## Round 5 — Request Headers & Query Params (Done Partly)
128173

129174
### What's Implemented

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ Every request, collection, environment, and workspace is a human-readable TOML f
9595

9696
- [x] **Round 1** - Core Request Engine (URL bar, HTTP executor, response viewer, syntax highlighting)
9797
- [x] **Round 2** - Environment Variables (`{{variable}}` interpolation, env switcher, secret vars) (partly - [more details here](SPEC.md#implementation-tasks-1))
98-
- [ ] **Round 3** - Collections & Workspaces (sidebar tree, tabs, file persistence)
98+
- [x] **Round 3** - Collections & Workspaces (sidebar tree, tabs, file persistence)
9999
- [ ] **Round 4** - Authentication (Basic, Bearer, API Key, OAuth 2.0, Digest)
100100
- [ ] **Round 5** - Request Headers & Query Params (key-value editors, autocomplete, bidirectional URL sync)
101101
- [ ] **Round 6** - Request Body Editor (JSON, Form, Multipart, GraphQL, Raw, Binary)

SPEC.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -908,15 +908,15 @@ pub struct RequestTab {
908908

909909
### Implementation Tasks
910910

911-
- [ ] Implement sidebar tree widget with collapse/expand
912-
- [ ] Implement collection CRUD (new, rename, delete, duplicate)
913-
- [ ] Implement folder support inside collections
914-
- [ ] Implement request tabs in main panel
915-
- [ ] Implement workspace switcher popup
916-
- [ ] Implement sidebar fuzzy search
917-
- [ ] Implement file persistence for collections (TOML files)
918-
- [ ] Implement auto-save on request modification
919-
- [ ] Implement workspace-level CRUD
911+
- [x] Implement sidebar tree widget with collapse/expand
912+
- [x] Implement collection CRUD (new, rename, delete, duplicate)
913+
- [x] Implement folder support inside collections
914+
- [x] Implement request tabs in main panel
915+
- [x] Implement workspace switcher popup
916+
- [x] Implement sidebar fuzzy search
917+
- [x] Implement file persistence for collections (TOML files)
918+
- [x] Implement auto-save on request modification
919+
- [x] Implement workspace-level CRUD
920920
- [ ] Display unsaved indicator (`*`) on dirty requests/tabs
921921

922922
---

0 commit comments

Comments
 (0)