217 lines
7.9 KiB
Markdown
217 lines
7.9 KiB
Markdown
# Alpha Workspace Identity Implementation Plan
|
|
|
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox syntax for tracking.
|
|
|
|
**Goal:** Give every managed Дело a durable UUID identity and publish lifecycle events that let alpha features follow a case across rename, Trash, restore, and path reuse.
|
|
|
|
**Architecture:** The filesystem remains the source of truth for whether a case folder exists. A UUID marker inside each case is the relation identity; Desktop metadata indexes that marker and paths remain presentation caches.
|
|
|
|
**Tech Stack:** Go, Wails bindings, JSON metadata, Go tests.
|
|
|
|
## Global Constraints
|
|
|
|
- A new Дело is created only by an explicit user action.
|
|
- workspaceId is UUID v4; a path is never relation identity.
|
|
- The marker is .verstak/workspace.json inside the case.
|
|
- Existing writable cases migrate without data loss; non-writable cases are view-only.
|
|
- Use TDD and make one commit per task.
|
|
|
|
---
|
|
|
|
### Task 1: Store and resolve durable workspace identities
|
|
|
|
**Files:**
|
|
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/core/workspace/manager.go
|
|
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/core/workspace/manager_test.go
|
|
|
|
**Interfaces:**
|
|
- Produces Workspace.ID string and Metadata.WorkspaceID string.
|
|
- Produces func (m *Manager) EnsureWorkspaceIdentity(name string) (WorkspaceIdentity, error).
|
|
- Produces func (m *Manager) ListWorkspaceIdentities() ([]WorkspaceIdentity, error).
|
|
|
|
- [ ] **Step 1: Write the failing identity tests**
|
|
|
|
Add tests proving creation writes a UUID marker, rename retains it, and a newly
|
|
created folder after external removal gets another UUID:
|
|
|
|
~~~go
|
|
ws, err := m.CreateWorkspace("Clients", "default")
|
|
if err != nil { t.Fatal(err) }
|
|
first, err := m.EnsureWorkspaceIdentity(ws.Name)
|
|
if err != nil || first.WorkspaceID == "" { t.Fatalf("identity = %+v, %v", first, err) }
|
|
if err := m.RenameWorkspace("Clients", "Clients-2026"); err != nil { t.Fatal(err) }
|
|
renamed, _ := m.EnsureWorkspaceIdentity("Clients-2026")
|
|
if renamed.WorkspaceID != first.WorkspaceID { t.Fatal("rename changed workspace ID") }
|
|
~~~
|
|
|
|
- [ ] **Step 2: Run the focused test to verify it fails**
|
|
|
|
Run:
|
|
|
|
~~~bash
|
|
cd /home/mirivlad/git/verstak2/verstak-desktop
|
|
GOCACHE=/tmp/verstak-go-cache go test ./internal/core/workspace -run 'Test.*Workspace.*Identity' -count=1
|
|
~~~
|
|
|
|
Expected: FAIL because WorkspaceIdentity and EnsureWorkspaceIdentity do not exist.
|
|
|
|
- [ ] **Step 3: Write the minimal marker implementation**
|
|
|
|
Add these types and use them in CreateWorkspace, GetWorkspaceMetadata, and
|
|
workspace listing:
|
|
|
|
~~~go
|
|
type WorkspaceIdentity struct {
|
|
WorkspaceID string `json:"workspaceId"`
|
|
RootPath string `json:"rootPath"`
|
|
State string `json:"state"`
|
|
}
|
|
|
|
const workspaceIdentityRelativePath = ".verstak/workspace.json"
|
|
|
|
func (m *Manager) EnsureWorkspaceIdentity(name string) (WorkspaceIdentity, error) {
|
|
// Validate the folder, read the in-folder marker, and atomically create
|
|
// a UUID-v4 marker only when absent and writable.
|
|
}
|
|
~~~
|
|
|
|
Write the same ID to central metadata only as an index. Never recover an ID from
|
|
stale path-keyed metadata when the in-folder marker is absent.
|
|
|
|
- [ ] **Step 4: Run green and commit**
|
|
|
|
Run:
|
|
|
|
~~~bash
|
|
GOCACHE=/tmp/verstak-go-cache go test ./internal/core/workspace -count=1
|
|
git add internal/core/workspace/manager.go internal/core/workspace/manager_test.go
|
|
git commit -m "feat: add durable workspace identities"
|
|
~~~
|
|
|
|
Expected: tests pass and the commit contains only workspace identity changes.
|
|
|
|
### Task 2: Expose identity and lifecycle events through Desktop
|
|
|
|
**Files:**
|
|
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/api/app.go
|
|
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/api/app_test.go
|
|
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/frontend/src/App.svelte
|
|
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/frontend/wailsjs/go/api/App.d.ts
|
|
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/frontend/wailsjs/go/models.ts
|
|
|
|
**Interfaces:**
|
|
- Produces ListWorkspaceIdentities() ([]workspace.WorkspaceIdentity, string).
|
|
- Lifecycle payloads include workspaceId and workspaceRootPath; Trash also includes trashId.
|
|
- Produces workspace.restored and workspace.purged events.
|
|
|
|
- [ ] **Step 1: Add failing API tests**
|
|
|
|
Extend TestWorkspaceAPIPublishesLifecycleEvents:
|
|
|
|
~~~go
|
|
if got := received["workspace.created"]["workspaceId"]; got == "" {
|
|
t.Fatal("workspace.created must include workspaceId")
|
|
}
|
|
if got := received["workspace.renamed"]["workspaceId"]; got != createdID {
|
|
t.Fatalf("rename ID = %v, want %s", got, createdID)
|
|
}
|
|
~~~
|
|
|
|
Add restore and purge tests around RestoreVaultTrash and DeleteVaultTrash that
|
|
assert the original UUID and trash ID are emitted.
|
|
|
|
- [ ] **Step 2: Run red**
|
|
|
|
Run:
|
|
|
|
~~~bash
|
|
GOCACHE=/tmp/verstak-go-cache go test ./internal/api -run 'TestWorkspaceAPI.*Lifecycle' -count=1
|
|
~~~
|
|
|
|
Expected: FAIL because lifecycle payloads only identify a path.
|
|
|
|
- [ ] **Step 3: Publish complete identity lifecycle**
|
|
|
|
Resolve the marker before publishing create, rename, selected, and trashed
|
|
events. Detect workspace Trash metadata in RestoreVaultTrash and
|
|
DeleteVaultTrash, then publish:
|
|
|
|
~~~go
|
|
map[string]interface{}{
|
|
"operation": "restore",
|
|
"workspaceId": identity.WorkspaceID,
|
|
"workspaceRootPath": restoredRoot,
|
|
"trashId": trashID,
|
|
}
|
|
~~~
|
|
|
|
Change App.svelte workspace-node conversion to preserve workspace.id while using
|
|
rootPath only for selection and display.
|
|
|
|
- [ ] **Step 4: Run green and commit**
|
|
|
|
Run:
|
|
|
|
~~~bash
|
|
GOCACHE=/tmp/verstak-go-cache go test ./internal/api ./internal/core/workspace -count=1
|
|
git add internal/api/app.go internal/api/app_test.go frontend/src/App.svelte frontend/wailsjs/go/api/App.d.ts frontend/wailsjs/go/models.ts
|
|
git commit -m "feat: publish workspace identity lifecycle"
|
|
~~~
|
|
|
|
Expected: focused API and workspace tests pass.
|
|
|
|
### Task 3: Migrate legacy references and repair duplicate IDs
|
|
|
|
**Files:**
|
|
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/core/workspace/manager.go
|
|
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/core/workspace/manager_test.go
|
|
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/api/app.go
|
|
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/api/app_test.go
|
|
|
|
**Interfaces:**
|
|
- Produces RepairWorkspaceIdentity(keepRootPath, regenerateRootPath string) string.
|
|
- Identity states are active, unavailable, or duplicate.
|
|
- Later Inbox and Activity plans consume workspaceId through ListWorkspaceIdentities.
|
|
|
|
- [ ] **Step 1: Write failing duplicate and legacy tests**
|
|
|
|
Create two folders with copied marker data and assert neither is an active
|
|
relation target. Create a legacy path-keyed capture fixture and assert a
|
|
resolved path gets an ID while a missing path becomes unavailable:
|
|
|
|
~~~go
|
|
if errStr := app.RepairWorkspaceIdentity("Original", "Copied"); errStr != "" {
|
|
t.Fatal(errStr)
|
|
}
|
|
if originalID == copiedID { t.Fatal("repair must issue a new ID") }
|
|
~~~
|
|
|
|
- [ ] **Step 2: Run red**
|
|
|
|
Run:
|
|
|
|
~~~bash
|
|
GOCACHE=/tmp/verstak-go-cache go test ./internal/core/workspace ./internal/api -run 'Test.*(Duplicate|Legacy).*Workspace' -count=1
|
|
~~~
|
|
|
|
Expected: FAIL because copied markers are accepted as ordinary folders.
|
|
|
|
- [ ] **Step 3: Implement deterministic repair**
|
|
|
|
Group active roots by marker UUID during identity listing. Mark a group with
|
|
more than one root duplicate and exclude it from assignment targets.
|
|
RepairWorkspaceIdentity verifies the duplicate, keeps the first marker, creates
|
|
a new marker for the second root, and moves no relation data.
|
|
|
|
- [ ] **Step 4: Run green and commit**
|
|
|
|
Run:
|
|
|
|
~~~bash
|
|
GOCACHE=/tmp/verstak-go-cache go test ./internal/core/workspace ./internal/api -count=1
|
|
git add internal/core/workspace/manager.go internal/core/workspace/manager_test.go internal/api/app.go internal/api/app_test.go
|
|
git commit -m "feat: repair duplicate workspace identities"
|
|
~~~
|
|
|
|
Expected: legacy, path-reuse, and duplicate-ID tests pass.
|