7.9 KiB
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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.