verstak-docs/docs/superpowers/plans/2026-07-12-alpha-workspace-...

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.