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
20 changes: 19 additions & 1 deletion docs/development-storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,29 @@ user-visible latency.

Plain browser development installs a small mock IPC adapter with in-memory
workspace, transcripts, and sidebar width. Separate tabs do not share mutations.
Reloading resets them; existing browser localStorage is untouched. Mock replies
Reloading resets them, including project display-name changes; existing browser
localStorage is untouched. Rename regressions may install a test-local persisted
name store to verify that a fresh workspace consumes confirmed labels; that fake
does not change the ordinary sandbox's reset behavior or prove disk persistence. Mock replies
are generated locally, with no model calls, credentials, real filesystem writes,
or SSH connections. This is a playground, not a substitute for native integration
checks or deterministic reproduction scenarios.

## Project display names

Project rename persists only an optional display name in the existing project
registry, for local and SSH projects alike. Records without a saved name keep
their location-derived default. Reimporting an existing identity preserves its
custom name; renaming does not move folders, rewrite Pi transcripts, change
session storage, or reconnect SSH.

Native development keeps confirmed names in its current disposable profile,
including across frontend reloads/HMR and fresh registry reads. Restarting the
native process intentionally creates a fresh profile and resets those names.
Production registry names survive application restarts. Native storage tests
verify persistence by reading the same saved registry again, independently of
the browser fake and development-profile reset policy.

## Production and architecture

The native `dev` compile-time configuration selects ephemeral storage; production
Expand Down
24 changes: 22 additions & 2 deletions docs/keyboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,30 @@ safety rules above. State pairs share one binding.

Palette-only target commands are **Focus Composer**, **Focus Sidebar**,
**Focus Transcript**, **Move Project Up**, **Move Project Down**,
**Previous Session**, **Next Session**, **Remove Project**, **Clear Queued Messages**,
**Previous Session**, **Next Session**, **Rename Project**, **Remove Project**, **Clear Queued Messages**,
**Show Update Status**, and **Check for Updates**. They have no global shortcut. Session/project pickers expose their dynamic results
only within their sublists; individual rows are not root commands.

## Project display names

**Rename Project…** is in each project row's context menu, between **New Session**
and **Remove Project**. It targets that row without switching projects or sessions.
The palette's **Rename Project** targets the active project and shows its name and
location to distinguish duplicate labels. It has no new shortcut; `⌘⇧R` still
renames the session.

The compact dialog selects the current name (or that project's retained draft).
Enter or `⌘Enter` saves; IME composition does not submit. Cancel or Escape keeps
unfinished text for reopening and restores valid origin focus or the composer.
Names are trimmed, nonblank, and limited to 240 UTF-16 code units; duplicate
labels, Unicode, punctuation, and internal spaces are allowed. A display-name
change does not move the folder or change project, session, or runtime identity.

While saving, repeated submission and dismissal are blocked. An inline failure
keeps the draft and offers **Retry**. After 10 seconds without confirmation,
**Close** permits leaving the dialog while the write finishes; it does not cancel
the save, and another save for that project is blocked until it settles.

## Local surfaces

Activating a focused project header makes that project active and expands it. Its
Expand All @@ -107,7 +127,7 @@ palette unless their launcher appears above.
| Surface | Target bindings |
| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| Transcript Find | Enter / Shift+Enter selects next / previous occurrence and wraps while Find has focus; Escape closes only when Find is the topmost eligible surface. |
| Rename | Enter or `⌘Enter` commits; Escape cancels. Opening the palette does not blur-commit. |
| Session rename | Enter or `⌘Enter` commits; Escape cancels. Opening the palette does not blur-commit. |
| Menus and context menus | Arrows, Enter, and their existing typeahead; Escape dismisses that menu. |
| Remote project dialog | Enter/`⌘Enter` submits where applicable; `⌘↑` goes to parent; Escape closes only when dismissible. |
| Pi prompts | Existing arrows/Enter selection; `⌘Enter` primary submit; `⌘⇧Enter` negative confirmation; Escape cancels only that prompt when eligible. |
Expand Down
3 changes: 3 additions & 0 deletions src-tauri/src/dev_workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ pub fn seed(profile: &StorageProfile) -> Result<(), String> {
}
registry.projects.push(ProjectRecord {
path: project_path,
name: None,
collapsed: false,
remote: None,
});
Expand Down Expand Up @@ -201,6 +202,7 @@ fn seed_performance(profile: &StorageProfile) -> Result<(), String> {
}
registry.projects.push(ProjectRecord {
path,
name: None,
collapsed: false,
remote: None,
});
Expand All @@ -212,6 +214,7 @@ fn seed_performance(profile: &StorageProfile) -> Result<(), String> {
serde_json::to_string(&(connection_string, working_directory)).map_err(seed_error)?;
registry.projects.push(ProjectRecord {
path: format!("ssh:{remote_path}"),
name: None,
collapsed: false,
remote: Some(RemoteProjectRecord {
connection_string: connection_string.into(),
Expand Down
1 change: 1 addition & 0 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ pub fn run() {
storage::import_remote_project,
storage::load_workspace,
storage::register_session,
storage::rename_project,
storage::remove_project,
storage::reorder_projects,
storage::set_active_project,
Expand Down
2 changes: 2 additions & 0 deletions src-tauri/src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ fn registry_version() -> u8 {
#[serde(rename_all = "camelCase")]
pub struct ProjectRecord {
pub path: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default)]
pub collapsed: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
Expand Down
15 changes: 14 additions & 1 deletion src-tauri/src/storage/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ mod registry;
mod sessions;

use projects::{
import_project_inner, import_remote_project_inner, remove_project_inner,
import_project_inner, import_remote_project_inner, remove_project_inner, rename_project_inner,
reorder_projects_inner, set_active_project_inner, set_project_collapsed_inner, snapshot,
};
use registry::{archive_revision, load_project_registry, mark_archive_intent};
Expand Down Expand Up @@ -62,6 +62,19 @@ pub async fn import_remote_project(
.await
}

#[tauri::command]
pub async fn rename_project(
telemetry: State<'_, Telemetry>,
telemetry_context: Option<TraceContext>,
path: String,
name: String,
) -> Result<String, String> {
let _span = telemetry_context
.as_ref()
.and_then(|context| telemetry.start_command_span(context, "rename_project"));
run_storage_worker(move || rename_project_inner(path, name)).await
}

#[tauri::command]
pub async fn remove_project(
telemetry: State<'_, Telemetry>,
Expand Down
Loading
Loading