diff --git a/specs/gh-46/PRODUCT.md b/specs/gh-46/PRODUCT.md new file mode 100644 index 00000000..45cb8d64 --- /dev/null +++ b/specs/gh-46/PRODUCT.md @@ -0,0 +1,49 @@ +# Add Workspace dialog with Cloud Sync setup + +Issue: https://github.com/bholmesdev/hubble.md/issues/46 + +## Summary + +Desktop users add folders through an Add Workspace dialog instead of a bare folder picker. The default path remains non-invasive, while optional Agent Instructions and Cloud Sync setup create support files only after the user opts into them. + +## Problem + +The desktop app currently treats opening a folder and adding a Workspace as the same direct picker action. Users need a clear setup surface where they can choose a folder, decide whether to add Workspace capabilities, and connect Cloud Sync without surprise writes. + +## Behavior + +1. The workspace switcher action says `Add Workspace...`, not `Add folder...`. +2. The empty desktop sidebar primary action says `Add Workspace` and opens the same dialog. +3. Opening the dialog does not start a folder picker immediately. The dialog presents a folder field with a `Choose...` button. +4. Choosing a folder shows the selected path and uses the folder basename as the default Workspace name wherever a new remote Workspace name is needed. +5. Canceling the folder picker returns to the dialog without changing any selections. +6. Canceling the dialog leaves the current open folder, recent Workspace list, and filesystem unchanged. +7. Confirming with only a folder selected opens that folder in the editor and does not create `.hubble/`, `.hubble/config.json`, or `.hubble/state.json`. +8. The dialog includes an `Add agent instructions` option. Its helper copy says these are lightweight agent skills for coding agents to use Hubble features inside this workspace, like embedding custom UIs. +9. If `Add agent instructions` is selected, confirm creates the required instruction files and then opens the folder. If writing those files fails, the folder does not silently become partially configured; the user sees an error and can retry or cancel. +10. The dialog includes `Connect Cloud Sync` or `Enable Cloud Sync`. Cloud Sync fields stay hidden until the user selects it. +11. When Cloud Sync is enabled, the user enters a Convex deployment URL. The URL validates on blur and shows pending, valid, and error states inline. +12. A valid deployment URL fetches available remote Workspaces. While loading, the remote Workspace chooser is disabled and shows a loading state. +13. Fetch failure keeps the typed URL, explains the failure, and exposes retry without closing the dialog. +14. The user can select an existing remote Workspace or choose `Create new`. +15. `Create new` shows a Workspace name input defaulting to the folder basename. Empty names block connect with inline validation. +16. `Auto sync` defaults on. Its tooltip explains files can still be synced manually with the Hubble CLI. +17. The final primary action is `Connect` when Cloud Sync is enabled, and it is disabled until required fields are valid. +18. Pressing `Connect` writes Cloud Sync configuration, creates sync state if missing, runs initial sync, and opens the folder when setup succeeds. +19. Initial sync uses the existing reconciliation behavior: local-only files push, remote-only files pull, unchanged files remain unchanged, conflicts preserve both, and assets follow current asset sync rules. +20. If daemon registration fails after Cloud Sync connects, the folder still opens as connected. The user sees an error with retry for background sync registration. +21. Turning `Auto sync` off during setup still connects Cloud Sync and allows manual sync through the Hubble CLI. +22. Workspace settings expose Cloud Sync details for a connected Workspace: deployment URL, remote Workspace, background sync state, retry, disconnect, and advanced read-only ids with copy actions. +23. Workspace settings show no Cloud Sync controls for a Plain Folder beyond an action to start Cloud Sync setup. +24. Disconnecting Cloud Sync removes Cloud Sync linkage while preserving local files and leaves a clear local-only state. +25. Relinking to a different deployment URL or remote Workspace requires a confirmation because it may merge different remote files into the same local folder. +26. Keyboard users can complete setup without a mouse: focus starts at the first actionable field, Tab order follows the visual order, Escape closes after confirmation when setup is dirty, and Enter submits only when valid. +27. Reduced-motion users do not see animated state changes beyond instant show/hide transitions. + +## Design context + +Use existing Hubble desktop patterns: the workspace switcher menu remains the entry point, shared modal/button/input primitives shape the dialog, inline validation stays close to the related field, and destructive or relink actions use explicit confirmation. + +## UX validation + +Open desktop with no folder selected, choose Add Workspace, cancel at each step, then add a Plain Folder and verify no `.hubble/` directory appears. Repeat with Cloud Sync enabled against a test Convex deployment: validate URL states, existing versus new remote Workspace selection, disabled Connect states, initial sync result, daemon failure retry messaging, and settings visibility. diff --git a/specs/gh-46/TECH.md b/specs/gh-46/TECH.md new file mode 100644 index 00000000..37f182a7 --- /dev/null +++ b/specs/gh-46/TECH.md @@ -0,0 +1,70 @@ +# Add Workspace dialog with Cloud Sync setup + +Issue: https://github.com/bholmesdev/hubble.md/issues/46 +Product spec: `specs/gh-46/PRODUCT.md` +Base commit: `e352538fcac0826efc4518ad8cdc479df9ea2a61` + +## Context + +This builds the desktop UI and IPC layer for the folder-first Cloud Sync model from issue #45. The user-visible behavior is defined in `PRODUCT.md`; this plan references its Behavior numbers. + +Current desktop flow is direct: `WorkspaceSwitcher` renders `Add folder...` and calls `openWorkspace()` ([apps/desktop/src/components/WorkspaceSwitcher.tsx](https://github.com/bholmesdev/hubble.md/blob/e352538fcac0826efc4518ad8cdc479df9ea2a61/apps/desktop/src/components/WorkspaceSwitcher.tsx#L42)), while `openWorkspace()` immediately calls `desktopApi.openFolderPicker()` when no path is provided ([apps/desktop/src/store/actions.ts](https://github.com/bholmesdev/hubble.md/blob/e352538fcac0826efc4518ad8cdc479df9ea2a61/apps/desktop/src/store/actions.ts#L102)). Electron exposes only a folder picker IPC today ([apps/desktop/electron/main.ts](https://github.com/bholmesdev/hubble.md/blob/e352538fcac0826efc4518ad8cdc479df9ea2a61/apps/desktop/electron/main.ts#L746)). + +The sync package already models optional nested `cloudSync` ([packages/sync/src/types.ts](https://github.com/bholmesdev/hubble.md/blob/e352538fcac0826efc4518ad8cdc479df9ea2a61/packages/sync/src/types.ts#L3)) and config helpers create/remove it ([packages/sync/src/config.ts](https://github.com/bholmesdev/hubble.md/blob/e352538fcac0826efc4518ad8cdc479df9ea2a61/packages/sync/src/config.ts#L56)). `init()` currently creates or reuses a remote Workspace by name, writes sync state, and writes Cloud Sync config ([packages/sync/src/sync.ts](https://github.com/bholmesdev/hubble.md/blob/e352538fcac0826efc4518ad8cdc479df9ea2a61/packages/sync/src/sync.ts#L20)). Convex client/backend APIs expose get/create/list Workspace primitives ([packages/convex-client/src/index.ts](https://github.com/bholmesdev/hubble.md/blob/e352538fcac0826efc4518ad8cdc479df9ea2a61/packages/convex-client/src/index.ts#L20), [packages/sync-backend/convex/sync.ts](https://github.com/bholmesdev/hubble.md/blob/e352538fcac0826efc4518ad8cdc479df9ea2a61/packages/sync-backend/convex/sync.ts#L62)). + +Dependency: implement after issue #45 lands or adapt to its final exported CLI/sync APIs. + +## Affected apps and packages + +- `apps/desktop`: Add dialog UI, state orchestration, settings UI, Electron IPC, daemon registration calls, desktop validation. +- `packages/sync`: Reuse nested `cloudSync` types/config helpers; likely add explicit connect-existing/create-new helpers if issue #45 does not expose them. +- `packages/convex-client`: Ensure desktop can validate deployment URLs and list/create remote Workspaces without duplicating Convex client code. +- `packages/ui`: Reuse `Modal`, `Button`, `Input`, and existing menu/sidebar primitives; add only small primitives if the dialog needs reusable checkbox/tooltip/select controls. +- `packages/cli`: No primary UI work, but keep manual sync behavior compatible with `cloudSync.backgroundSync=false`. + +## Module architecture + +- `apps/desktop/src/components/AddWorkspaceDialog.tsx`: Controlled dialog for folder choice, optional Agent Instructions, Cloud Sync setup, validation states, and submit actions. +- `apps/desktop/src/components/WorkspaceSwitcher.tsx`: Rename menu item and open the dialog instead of calling `openWorkspace()` directly for new Workspaces. +- `apps/desktop/src/components/Sidebar.tsx`: Empty-state Add Workspace button opens the dialog and preserves current folder behavior after submit. +- `apps/desktop/src/store/actions.ts`: Split direct opening from setup. Keep `openWorkspace(path)` as the low-level folder loader; add `showAddWorkspaceDialog()`, `chooseWorkspaceFolder()`, and `completeAddWorkspace()`. +- `apps/desktop/src/store/state.ts` plus persistence if needed: Track dialog open state and in-flight setup state outside persisted recent Workspaces. +- `apps/desktop/src/desktopApi/types.ts`, `apps/desktop/electron/preload.ts`, `apps/desktop/electron/main.ts`: Add IPC for reading/writing `.hubble` support files, Cloud Sync connect/setup, initial sync, and daemon registration. Grant filesystem access only after the user chooses the folder. +- `apps/desktop/src/components/WorkspaceSettings.tsx`: New settings surface or section attached to the existing app chrome/menu path, exposing Cloud Sync details and disconnect/retry/relink flows. +- `packages/convex-client`: Export a small helper for deployment URL validation/listing if desktop should not import `ConvexHttpClient` directly. + +Data flow: dialog gathers local choices, Electron grants folder access, renderer validates remote choices through a typed API, submit asks the main process or sync facade to write config/state and run initial sync, then renderer opens the folder with existing `openWorkspace(path)`. + +## Detailed plan + +1. Replace visible copy for Behavior 1-2 and route both entry points to dialog state. +2. Build `AddWorkspaceDialog` with folder picker, dirty/cancel handling, inline validation, keyboard focus, and reduced-motion-safe show/hide states for Behavior 3-7 and 26-27. +3. Add Agent Instructions option behind a narrow writer API. Keep support-file creation separate from opening the folder so Behavior 7 and 9 can be tested independently. +4. Add Cloud Sync subform: URL input validates on blur, remote Workspace list loads after valid URL, create-new path uses folder basename, and Auto sync defaults true for Behavior 10-17. +5. Reuse issue #45 sync connect APIs. If needed, add `connectCloudSync({ workspacePath, deploymentUrl, workspaceId | workspaceName, backgroundSync })` in `packages/sync` so desktop does not rebuild config/state logic. +6. Run initial sync through the existing reconciliation path after config/state creation for Behavior 18-19. +7. Add daemon registration IPC/facade. Treat registration as a post-connect step: persist Cloud Sync success first, then show retry state on daemon failure for Behavior 20-21. +8. Add Workspace settings section with connected, local-only, retry, disconnect, copy id, and relink confirmation states for Behavior 22-25. +9. Keep all layout CSS using logical spacing props such as `padding-inline`, `padding-block`, `margin-inline`, and `margin-block`. + +Tradeoff: keep the first implementation desktop-only. Web already requires synced Workspaces and has separate connect/open screens, so sharing the whole dialog would blur surface-specific behavior. + +## Testing and validation + +- Unit/component tests for `AddWorkspaceDialog` states: no folder, picker cancel, Plain Folder submit, Cloud Sync disabled/enabled, invalid URL, fetch failure, existing remote selection, create-new validation, Auto sync default. +- Store/action tests around `completeAddWorkspace()` to assert Behavior 6-9 and no recent Workspace mutation until successful folder open. +- Sync facade tests for Behavior 18-21: config/state writes, existing versus new remote Workspace, initial sync call, `backgroundSync=false`, daemon registration failure does not remove `cloudSync`. +- Settings tests for Behavior 22-25: local-only state, connected state, copyable ids, retry, disconnect, relink confirmation. +- Manual desktop validation: open with no folder, run the UX validation from `PRODUCT.md`, and inspect the selected folder for `.hubble` absence/presence. +- Commands: use `pnpm check` during iteration, then `pnpm build:desktop` before implementation PR review. + +## Risks and mitigations + +- Issue #45 API drift: implement #46 after #45 merges or isolate all sync calls behind one desktop facade. +- Accidental `.hubble` writes on Plain Folder open: add direct tests around Behavior 7 and keep support-file writers out of the low-level `openWorkspace(path)`. +- Partial Cloud Sync setup: order operations so config/state write and initial sync errors surface in the dialog, while daemon registration failure becomes retryable settings state. +- Relink data surprises: require confirmation and route through the same reconciliation path as initial sync. + +## Parallelization + +Useful after issue #45 lands. One agent can own desktop dialog/state and another can own Electron/sync facade plus settings, with shared contract types agreed first. Merge sync facade before wiring dialog submit.