feat: add workspace management editor

This commit is contained in:
2026-08-04 06:27:59 +02:00
parent b686ffe271
commit 48fd316b90
10 changed files with 744 additions and 7 deletions
+50
View File
@@ -9,6 +9,14 @@ export interface WorkspaceDraft {
updatedAt: string;
}
/** A publishable deletion intent; it deliberately carries no workspace body. */
export interface WorkspaceDeletionDraft {
id: string;
baseCommit: string;
baseBlob: string;
updatedAt: string;
}
export const WORKSPACE_SUMMARY_ERROR = "Could not load workspace registry. Please retry.";
export const WORKSPACE_POLICY_ERROR = "Could not load selected workspace policy. Please retry.";
@@ -114,6 +122,7 @@ export const workspacePolicyGate = {
const PREFIX = "thothii.workspace-registry.v1";
const DRAFT_PREFIX = `${PREFIX}.draft.`;
const DELETE_DRAFT_PREFIX = `${PREFIX}.delete.`;
function storage(): Storage | undefined {
try { return window.localStorage; } catch { return undefined; }
@@ -342,6 +351,25 @@ function key(id: string): string {
return `${DRAFT_PREFIX}${encodeURIComponent(id)}`;
}
function deletionKey(id: string): string {
return `${DELETE_DRAFT_PREFIX}${encodeURIComponent(id)}`;
}
function normalizeDeletion(value: unknown): WorkspaceDeletionDraft | undefined {
const source = exactRecord(value, ["id", "baseCommit", "baseBlob", "updatedAt"]);
const id = workspaceId(source?.id);
const baseCommit = typeof source?.baseCommit === "string" && /^[0-9a-f]{40}$/.test(source.baseCommit)
? source.baseCommit
: undefined;
const baseBlob = typeof source?.baseBlob === "string" && /^[0-9a-f]{40}$/.test(source.baseBlob)
? source.baseBlob
: undefined;
const updatedAt = typeof source?.updatedAt === "string" && Number.isFinite(Date.parse(source.updatedAt))
? source.updatedAt
: undefined;
return id && baseCommit && baseBlob && updatedAt ? { id, baseCommit, baseBlob, updatedAt } : undefined;
}
/** Browser-only workspace drafts. Saving or editing one never calls the server. */
export const workspaceDrafts = {
load(id: string): WorkspaceDraft | undefined {
@@ -363,3 +391,25 @@ export const workspaceDrafts = {
try { storage()?.removeItem(key(id)); } catch { /* storage is optional */ }
},
};
/** Browser-only deletion drafts. Task 10 alone may publish one. */
export const workspaceDeletionDrafts = {
load(id: string): WorkspaceDeletionDraft | undefined {
try {
const raw = storage()?.getItem(deletionKey(id));
return raw ? normalizeDeletion(JSON.parse(raw)) : undefined;
} catch {
return undefined;
}
},
save(draft: WorkspaceDeletionDraft): void {
const safe = normalizeDeletion(draft);
if (!safe) return;
try { storage()?.setItem(deletionKey(safe.id), JSON.stringify(safe)); } catch { /* storage is optional */ }
},
discard(id: string): void {
try { storage()?.removeItem(deletionKey(id)); } catch { /* storage is optional */ }
},
};