Skip to content
Closed
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
29 changes: 22 additions & 7 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,25 @@ Glossary for shared terms across the project. Implementation details belong in c

## Flagged ambiguities

- **A Workspace is defined by its configuration, not by the cloud.** Don't conflate "is this a Workspace?" (does the folder have a `.hubble/` configuration) with "is it synced?" ([[Cloud Sync]] enabled). A desktop Workspace can be local-only and gain Cloud Sync later. *(Note: not yet true in code — `init()` requires a Convex backend to mint a `workspaceId`. This is the subject of an active spec; see the deferred-cloud-sync handoff.)*
- **"Open folder" (desktop runtime) vs "Workspace."** The desktop editor operates on any open folder path and reads/writes the filesystem directly; that folder may be a [[Workspace Folder]] or a [[Plain Folder]]. Say "open folder" for the runtime notion, "Workspace" for the configured logical entity.
- **"Open folder" (desktop runtime) vs "Workspace."** The desktop editor operates on any open folder path and reads/writes the filesystem directly. Say "open folder" for the runtime notion and "Workspace" for the folder-first product notion.

## Glossary

### Workspace

A logical container of Markdown Files and Assets, defined by a **workspace configuration** (`.hubble/config.json`) — *not* by the cloud. A Workspace has a stable identity from the moment it is created.
A logical container of Markdown Files and Assets. On desktop, a Workspace is a folder the user has added to Hubble. Adding a Workspace does not require `.hubble/config.json`; optional capabilities create configuration only when needed.

[[Cloud Sync]] is an optional capability layered on top. A Workspace may be **local-only** (configured, no cloud backend) or **synced** (bound to a Convex deployment, with a row in the `workspaces` table). On the **web** a Workspace is always synced — there is no local filesystem to fall back to. On **desktop** a Workspace can be created local-only and have Cloud Sync enabled later.
[[Cloud Sync]] is an optional capability layered on top. A Workspace may be **local-only** (no cloud backend) or **synced** (bound to a Convex deployment, with a row in the `workspaces` table). On the **web** a Workspace is always synced — there is no local filesystem to fall back to. On **desktop** a Workspace starts local-only and can have Cloud Sync enabled later.

The Workspace is the unit; the **access path differs by surface**. The web app reads/writes through the Convex backend. The desktop app reads/writes the [[Workspace Folder]] directly on disk (the working source of truth), with the background sync engine reconciling to Convex when Cloud Sync is on. Features that must run on both surfaces — notably [[Embed]]s — target the Workspace as the unit and resolve against whichever backend the current surface provides; they never assume Convex.

### Workspace Folder

The on-disk realization of a [[Workspace]]: a folder containing the workspace configuration at `.hubble/config.json`. **Configuration presence — not cloud binding — is what makes a folder a Workspace Folder.** When [[Cloud Sync]] is enabled the config also carries the Convex linkage (today: `workspaceId`, `workspaceName`); a local-only Workspace Folder has configuration without it. Multiple Workspace Folders across devices can map to the same synced Workspace.
The on-disk realization of a [[Workspace]]: a folder the desktop app has added to Hubble. The folder may have no `.hubble/` directory. `.hubble/config.json` is created only when an optional capability needs Workspace Configuration, such as [[Cloud Sync]], agent instructions, or embeds. Multiple Workspace Folders across devices can map to the same synced Workspace when they share Cloud Sync linkage.

### Plain Folder

A folder open in the desktop app with **no** workspace configuration (no `.hubble/`). It is not a [[Workspace]]: the desktop app reads and edits it as a general markdown viewer, nothing syncs, and Workspace-scoped features (e.g. [[Embed]]s) do not resolve. Adding a configuration promotes it to a [[Workspace Folder]].
A folder opened directly in the desktop app without being added as a Workspace. The desktop app may read and edit it as a general markdown viewer, nothing syncs, and Workspace-scoped features may be unavailable until the folder is added as a Workspace.

### Folder

Expand All @@ -37,7 +36,23 @@ _Avoid_: Compacted directory name

### Cloud Sync

The optional capability that binds a [[Workspace]] to a Convex deployment, enabling multi-device sync and web access. Required for web Workspaces; opt-in on desktop, where it can be enabled after a Workspace is created by supplying a Convex deployment URL. Whether Cloud Sync is on is orthogonal to whether a folder is a Workspace.
The optional capability that binds a [[Workspace]] to a Convex deployment, enabling multi-device sync and web access. Required for web Workspaces; opt-in on desktop, where it can be enabled after a Workspace is created by supplying a Convex deployment URL.

Cloud Sync configuration lives under `cloudSync` in `.hubble/config.json`:

```json
{
"cloudSync": {
"provider": "convex",
"deploymentUrl": "https://...",
"workspaceId": "convex workspace id",
"deviceId": "local sync device uuid",
"backgroundSync": true
}
}
```

`cloudSync.backgroundSync` controls automatic daemon sync only. Manual sync can still run when background sync is off. Turning off background sync preserves `cloudSync` and `.hubble/state.json`; disconnecting Cloud Sync removes the `cloudSync` configuration.

### Markdown File

Expand Down
19 changes: 19 additions & 0 deletions docs/adr/0009-folder-first-workspaces-and-deferred-cloud-sync.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Folder-first Workspaces and deferred Cloud Sync

Desktop Workspaces are folders added to Hubble. Adding a Workspace is non-invasive by default: Hubble does not create `.hubble/` files just to remember or edit the folder.

Workspace Configuration is deferred until a capability needs it. Capabilities such as Cloud Sync, agent instructions, or embeds may create `.hubble/config.json`; plain editing and sidebar navigation do not.

Cloud Sync is a nested Workspace capability, not the definition of a Workspace. When connected, `.hubble/config.json` stores `cloudSync.provider`, `cloudSync.deploymentUrl`, `cloudSync.workspaceId`, `cloudSync.deviceId`, and `cloudSync.backgroundSync`. The Convex `workspaceId` is the only remote Workspace id in this model, and `deviceId` is sync-scoped.

Turning off background sync preserves Cloud Sync configuration and `.hubble/state.json`, so manual sync can continue. Disconnecting Cloud Sync removes the `cloudSync` configuration while leaving local files and sync state intact.

Initial Cloud Sync setup uses the existing reconciliation rules: local-only files push, remote-only files pull, same-path same-hash files remain unchanged, same-path different-content files use conflict handling, and assets follow current asset sync rules.

## Consequences

- A desktop Workspace can exist without `.hubble/config.json`.
- `.hubble/config.json` presence means at least one optional Workspace capability is configured; it does not define Workspace existence.
- Web Workspaces remain synced-only because the web app has no local filesystem source of truth.
- UI copy should ask users to enable or connect Cloud Sync, not create a Workspace in the cloud.
- Relinking Cloud Sync is a distinct flow because it may merge a different remote Workspace into the same local folder.
64 changes: 64 additions & 0 deletions specs/gh-34/PRODUCT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Folder-first Workspaces and deferred Cloud Sync

## Summary

Desktop Workspaces are folders users add to Hubble, not cloud records and not `.hubble/` directories. Cloud Sync is an optional capability users can connect later without changing the local-first editing model.

## Problem

Today product language and flows can imply that a Workspace is created by Cloud Sync or by `.hubble/config.json`. That makes local-only desktop use feel provisional and makes later sync setup harder to reason about.

## Goals

- Adding a desktop Workspace is non-invasive by default.
- Cloud Sync setup is explicit, reversible, and independent from whether a folder is a Workspace.
- Users can choose an existing remote Workspace or create a new one when connecting Cloud Sync.
- Initial sync uses existing reconciliation behavior and does not imply local files are overwritten.
- Background sync is a preference, not the same thing as Cloud Sync linkage.

## Non-goals

- No multi-user account model.
- No token/auth flow changes.
- No new sync conflict policy.
- No support for preserving empty folders through Cloud Sync.
- No automatic relink to a different Convex deployment without confirmation.

## Behavior

1. When a desktop user chooses `Add Workspace`, Hubble asks for a folder.
2. Adding a folder succeeds without creating `.hubble/config.json`.
3. The added folder appears as a Workspace in desktop navigation immediately after selection.
4. Local-only Workspaces can create, edit, rename, and delete Markdown Files and Assets without Cloud Sync.
5. Workspace-scoped optional capabilities may create `.hubble/config.json` only when the user enables that capability.
6. The Add Workspace dialog offers optional setup for agent instructions.
7. The Add Workspace dialog offers optional Cloud Sync setup, hidden until the user chooses to connect it.
8. Cloud Sync setup asks for a Convex deployment URL.
9. On blur or submit, Hubble validates the Convex deployment URL and reports invalid, unreachable, or incompatible deployments in place.
10. After a valid URL, Hubble shows available remote Workspaces from that deployment.
11. The user can select an existing remote Workspace.
12. The user can choose `Create new` and enter a remote Workspace name, defaulting to the folder name.
13. Cloud Sync setup includes an `Auto sync` checkbox that defaults on.
14. The Auto sync explanation makes clear that manual sync remains available when auto sync is off.
15. Connecting Cloud Sync writes the linkage and runs initial sync.
16. Initial sync pushes local-only files, pulls remote-only files, leaves same-path same-hash files unchanged, and preserves same-path different-content files through conflict handling.
17. Assets follow the existing asset sync rules during initial sync.
18. If background sync registration succeeds, the Workspace syncs automatically after setup.
19. If background sync registration fails, Cloud Sync remains connected, the user sees the registration error, and a retry action is available.
20. A synced desktop Workspace shows its Convex deployment URL and linked remote Workspace in settings.
21. Settings show whether background sync is registered, based on live service state.
22. Turning off Auto sync preserves Cloud Sync linkage and sync state.
23. Manual sync remains available while Auto sync is off.
24. Disconnecting Cloud Sync removes the Cloud Sync linkage but keeps local files.
25. Editing the deployment URL or linked remote Workspace starts a relink flow with confirmation because it may merge different remote content into the same local folder.
26. Web Workspaces are always synced because web has no local filesystem Workspace source of truth.
27. A user can understand from labels and messages that `Workspace`, `Cloud Sync`, and `Auto sync` are separate states.

## UX validation

1. In desktop, add a folder as a Workspace and confirm no `.hubble/config.json` is created.
2. Enable agent instructions or another config-backed capability and confirm configuration is created only then.
3. Connect Cloud Sync with a valid Convex URL, create a new remote Workspace, and confirm initial files appear on web.
4. Connect Cloud Sync with an existing remote Workspace and confirm remote-only files pull locally.
5. Turn Auto sync off, run manual sync, and confirm the Workspace remains linked.
6. Disconnect Cloud Sync and confirm local files remain editable while web access stops for that local folder.
120 changes: 120 additions & 0 deletions specs/gh-34/TECH.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# Folder-first Workspaces and deferred Cloud Sync

## Context

Issue #34 defines desktop Workspaces as local folders first, with Cloud Sync as an optional nested capability. See `PRODUCT.md` for behavior.

Current commit: `4247f68c6fa5e63196dc3eefadb1192878720b69`.

Current architecture already has part of the model:

- `packages/sync/src/types.ts` defines optional `WorkspaceConfig.cloudSync`.
- `packages/sync/src/config.ts` reads missing config as `{}` and writes `cloudSync` only through `writeCloudSyncConfig`.
- `packages/sync/src/sync.ts` requires `cloudSync` only when running sync.
- `packages/cli/src/index.ts` has `hubble cloud create`, `connect`, `sync`, `watch`, and `disconnect`.
- `apps/www` stores a Convex URL and Workspace id because web Workspaces are synced-only.

The main mismatch is desktop configuration ownership. `apps/desktop/electron/main.ts` uses `.hubble/config.json` for desktop pinned notes with a `{ version, pinnedNotes }` shape, while `packages/sync` expects the same path to hold optional `cloudSync`. Before shipping the full flow, those shapes need one shared Workspace Configuration schema or separate paths.

## Affected apps and packages

- `apps/desktop`: Add Workspace dialog, settings UI, background sync registration state, and Workspace Configuration reads/writes.
- `apps/www`: No local-first changes; web remains synced-only. May need copy updates if shared labels change.
- `packages/sync`: Shared config schema, Cloud Sync connect/disconnect helpers, initial sync behavior, and tests.
- `packages/convex-client`: Remote Workspace listing and creation are already available through backend APIs; expose typed helpers if UI should avoid raw Convex calls.
- `packages/cli`: Keep manual Cloud Sync commands aligned with the desktop config schema and background sync semantics.
- `packages/sync-backend`: No expected schema change for this slice unless remote Workspace list/create APIs need additions.

## Module architecture

Use one shared Workspace Configuration contract.

- `packages/sync/src/types.ts`
- Own `WorkspaceConfigSchema`.
- Include optional `cloudSync`.
- Include any desktop config-backed capability fields that must share `.hubble/config.json`, or explicitly move desktop-only fields to a different file.
- `packages/sync/src/config.ts`
- Own all `.hubble/config.json` reads/writes used by sync-aware code.
- Preserve unknown or unrelated optional sections only if the shared schema intentionally supports them.
- `packages/sync/src/sync.ts`
- Keep `sync()` requiring `cloudSync`.
- Extract connect helpers that can create or link a remote Workspace and initialize `.hubble/state.json`.
- `apps/desktop/electron/main.ts`
- Stop owning an incompatible private schema for `.hubble/config.json`.
- Expose IPC for reading/writing Workspace Configuration through shared package code or an equivalent schema.
- `apps/desktop/src`
- Add Add Workspace dialog state for optional Cloud Sync fields.
- Add Workspace settings state for connected, disconnected, background sync registered, registration failed, and retry.
- `packages/cli/src/index.ts`
- Continue using shared config helpers for `cloud create`, `connect`, `sync`, `watch`, and `disconnect`.

## Detailed plan

1. Reconcile Workspace Configuration.
- Decide whether pinned notes belong in shared `.hubble/config.json` or a desktop-specific file.
- If shared, add `pinnedNotes` and config versioning to `WorkspaceConfigSchema`.
- If separate, move desktop pins away from `.hubble/config.json`.
- Add tests for reading old/missing config and writing config without enabling Cloud Sync.
2. Make desktop Add Workspace non-invasive.
- Ensure folder selection persists as a recent/current Workspace without writing `.hubble/config.json`.
- Keep file listing and editing independent from config presence.
- Verify Behavior 1-5.
3. Add Cloud Sync setup to Add Workspace.
- Validate deployment URL before showing remote Workspace choices.
- Fetch remote Workspaces after validation.
- Support existing remote Workspace selection and create-new name input.
- Default create-new name to the folder basename.
- Default Auto sync on.
4. Implement connect semantics.
- Write `cloudSync` config only after the user confirms setup.
- Create `.hubble/state.json` when missing.
- Run initial sync through existing reconciliation rules.
- Register background sync only after Cloud Sync linkage exists.
- Preserve linkage if registration fails, and surface retry state.
5. Add Workspace settings.
- Show deployment URL, linked remote Workspace, background sync registration state, and read-only ids with copy actions.
- Toggle Auto sync by changing background registration, not by deleting `cloudSync`.
- Disconnect Cloud Sync by removing only `cloudSync`.
- Gate URL/Workspace edits behind a relink confirmation.
6. Keep CLI aligned.
- Ensure `hubble cloud disconnect` removes `cloudSync` without deleting local files or `.hubble/state.json`.
- Add a background-sync option only if the daemon has a CLI-facing registration path.

## Testing and validation

- Behavior 1-5: desktop unit/integration coverage for adding a folder and confirming no config write occurs.
- Behavior 8-14: component tests for Cloud Sync setup validation, remote Workspace list, create-new state, and Auto sync default.
- Behavior 15-17: `packages/sync` tests for connect plus initial sync using existing file and asset reconciliation.
- Behavior 18-19: desktop tests around registration success/failure and retry state.
- Behavior 20-25: settings tests for display, Auto sync toggle, disconnect, and relink confirmation.
- Behavior 26-27: web smoke test for unchanged synced-only connection flow and copy review.

Run:

- `pnpm check` for quick iteration.
- `pnpm build:desktop` before final review.
- Desktop manual validation with a temporary folder, then a second synced device or web app to verify initial sync.
- Web validation through `apps/www` with `?test=1` when checking web visibility.

## Parallelization

Useful after the config decision is made:

- Agent 1: shared config/schema and sync tests.
- Agent 2: desktop Add Workspace and settings UI.
- Agent 3: CLI alignment and manual validation docs.

Do not parallelize before the config path decision; otherwise agents may encode incompatible `.hubble/config.json` assumptions.

## Risks and mitigations

- Data loss during first sync: reuse existing conflict handling and add explicit tests for same-path different-content files.
- Config schema collision: resolve desktop pins vs sync config before UI work.
- Partial setup failure: persist Cloud Sync linkage before background registration and expose retry.
- Relink surprises: require confirmation before changing deployment URL or remote Workspace.

## Follow-ups

- Pairing or token auth for protected Convex deployments.
- Empty folder preservation.
- More detailed background sync service observability.