* feat(workspace): set project location from the create-worktree host picker
Hosts that still say "Project location not set" now get an inline Set location action. It opens a nested dialog over Create worktree so the in-progress form stays put.
* fix(workspace): replace unset-location status copy with a button
Drop the redundant "Project location not set" caption and show a Set project location action with a hover tooltip instead.
* refactor(workspace): tighten the set-project-location dialog
- reuse CreateProjectParentBrowser instead of a second host-filesystem browse view
- drop ProjectLocationBrowseTarget; parseExecutionHostId already models it
- single setLocation path in RunTargetCombobox (row, button, Enter)
- memoize the default clone URL instead of scanning on every store update
- fix missing required props in the new composer-card test
* fix(workspace): close the correctness gaps in set-project-location
- Escape in the host browser now backs out to the form instead of dismissing
the dialog and discarding the half-filled path/clone URL. Radix dismisses
from a document-capture listener, so only preventDefault can stop it.
- Drop the stopPropagation guards: window-capture (the composer's Escape
handler) already ran by then, so they never protected it — the nestedDialogOpen
gate does. They did silently kill RemoteFileBrowser's own key handling.
- Hide Set project location for host-local repo:<id> projects (folder projects,
git repos with no remote). Linking on another host matches by project identity,
which those have none of, so the call could only ever toast an error.
- Drop a standalone placeholder setup once a repo projection covers the same
project+host, restoring the (projectId, hostId) uniqueness invariant. Setting a
location on a host with a pending setup was leaving a ghost that sorts first and
reads back as 'not set up'.
- Existing-folder submit label matched a catalog string reading 'Importing...'
* test(composer): follow the renamed local in the host-retarget source assertion
Follow-ups to the module extraction (#14252):
- Guard the lineage companion maps in worktree meta GC, and clear them
alongside a corrupt worktreeMeta so stale rows can't re-attach.
- Repair a renamed worktree's stale lineage.worktreeId and flag the save.
- Key the git-username cache by execution host + path so the same checkout
path on local/SSH/runtime hosts can't cross-hydrate usernames.
- Drop undefined keys before the automation update spread; a Partial with
an explicit undefined blanked stored values.
- Normalize pane identities in every workspaceSessionsByHostId partition,
not just the legacy blob, and use the merged leaf maps for lease and
acknowledgement remapping.
- Reject duplicate preferred leaf ids so two panes can't collapse onto one
pty/buffer/scrollback key.
- Let an explicit rightSidebarExplorerView outrank the legacy search-tab
fallback; the legacy migration now runs on the raw payload at load.
- Record why the mobile pairing migration leaves its sources in place.
- Trim persisted folderPath, normalize synthesized worktree visibility
preferences, validate notification settings field-by-field, strip
undefined optional keys before the SSH lease merge, reuse `now` for an
automation's updatedAt, and rename the pane alias registrar.
* refactor(persistence): extract modules to half persistence.ts
* refactor(persistence): tighten the extracted operations seam
Review follow-ups on the module extraction, all behavior-neutral.
The extracted operations read and mutate the Store's state object in place, but
every seam typed it as a bare PersistedState, so nothing at the boundary said a
caller must pass the live reference — a future caller handing over a clone would
have its writes silently dropped. Name that contract: StoreOwnedPersistedState
carries it to every operations interface and every mutating free function.
normalizePersistedPaneIdentityState and backfillFolderScopeConnectionIds stay on
PersistedState; they build a fresh state rather than mutating the Store's.
The six *PersistenceOperations wrappers were constructed per delegate call. They
are stateless today, so this was inert, but any future instance state would be
lost between calls. Memoize them, and mark state and gitUsernameCache readonly
so the compiler enforces the single-assignment invariant memoizing them relies
on.
Also: restore flushSshPtyConsumerRecovery, whose inlining left its rationale
duplicated at both call sites; document that migrateWorktreeIdentity's boolean
gates the caller's save, since the extracted function kept no docs of its own;
and merge a duplicate shared/types import that was failing lint under
--deny-warnings.
* delete plan doc
* refactor(persistence): add error recovery and improve field cleanup
- Rollback failed migrations to prevent corrupted state that blocks retry
- Gracefully skip malformed entries in normalization instead of aborting
- Strip retired fields to prevent orphaned state and sync issues
* refactor(persistence): drop the redundant persistence- filename prefix
The extracted modules already live in src/main/persistence/, so name
them after the domain they own. Point leftover shared/types imports
at the real type modules while touching those files.
* refactor(persistence): optimize lookups and fix unsanitized updates
- Use Maps instead of repeated array searches for O(1) lookups
- Apply sanitized updates instead of raw input in ui-state-update
- Compare fields directly rather than JSON strings to avoid false dirty states from persisted key ordering differences
* refactor(persistence): group modules into lifecycle folders
Move the 42 flat persistence modules into six folders named for what the
module does, and lift the Store class out of the barrel so persistence.ts
becomes an 8-line public surface.
Bodies are unchanged: every moved file diffs clean against HEAD once import
blocks are excluded. Only import specifiers were rewritten, by resolving each
one to an absolute path and mapping it through the move map.
Store keeps its existing max-lines suppression; its baseline entry is repathed
rather than re-added. Its 119-method public API sets a ~525-line floor, so it
cannot meet the 400-line cap without breaking the API for 153 importers.
* Sanitize worktree visibility sources and preferences on hydration
Ensure invalid or corrupted data from disk (untracked whitespace,
relative paths, bogus preference values) is cleaned during load
rather than corrupting the in-memory store.