# Task 10 — Workspace Registry Manager Publish UX ## Delivered - Added a typed `WorkspacePublishDialog` with an explicit two-stage flow: validate the canonical draft, then confirm publication. The dialog displays the action and pinned base revision before a request can be sent. - Connected the workspace editor's Publish action and staged deletion action to that dialog; local browser drafts remain local until the explicit confirmation. - Added registry pull, workspace bundle import, and Blob-URL export controls. Imports are saved as browser-only drafts and never publish automatically; export URLs are revoked after download. - Added field-level 409 conflict presentation with base, local, and registry values. The only recovery actions are Pull latest registry and Reload workspace; no automatic merge, overwrite, or re-publication occurs. - Kept diagnostics user-initiated and restricted UI/API draft data to canonical workspace fields. Conflict payloads now pass through the canonical draft sanitizer and reject unknown/secret fields before rendering. ## Review round 1 - Replaced the pull/reload-only conflict recovery with an explicit choice of the local draft or registry value for every changed field. A revised draft can be saved only after every field has a choice; it is rebased to the conflict's `actual.commit` and `actual.blob` and is never published automatically. - Kept normal validation and the explicit publish confirmation as mandatory steps after saving a resolution. Nothing silently discards the local draft or merges it into the registry. - Added typed expected/actual conflict revisions, displayed the active registry `status.head` commit, and whitelisted every canonical `diagnostics.*` leaf path structurally. ## TDD evidence - Wrote the publish-dialog and manager import/export tests before the implementation and observed the expected RED failures (missing dialog/import control). - Added a regression test for conflict payloads containing a secret field and observed it fail before wiring the conflict parser through the canonical sanitizer. - Added a regression test for a failed pull during conflict recovery and observed the original unhandled rejection before adding the redacted in-dialog error state. - Added review-round tests first for per-field local/registry selection, rebased draft saving without a second publish, active-commit rendering, and every canonical diagnostics conflict path; these initially failed against the pull/reload-only UI and narrow path parser. ## Review round 2 - Fixed recursive registry diffs so add/remove changes to optional nested diagnostics branches report their actual canonical paths instead of the fallback `workspace.id`. The regression cases cover both add and remove for `diagnostics.dwh_rest` and `diagnostics.vector_rest.reversible_probe`. - Extended the conflict-path allowlist to accept the optional `diagnostics` root and every optional diagnostics branch. The existing structural rebase now saves an explicitly selected branch (including an added or removed branch) in the revised browser draft, still pinned to the registry's actual revision and requiring normal validation and confirmation before publishing. - Wrote the backend/frontend cases first and observed the expected RED failures: backend conflict fields were `workspace.id`, while the frontend rejected the safe conflict payload before the resolution UI could render. ## Verification Run in `frontend/` after the final changes: ```text npx vitest run src/shell/WorkspacePublishDialog.test.tsx src/api/workspaces.test.ts src/shell/WorkspaceManager.test.tsx src/workspaces/drafts.test.ts # 4 files passed, 47 tests passed npx tsc -b # exit 0 ``` ```text npx vitest run # 51 files passed, 398 tests passed ``` Round-2 focused verification: ```text backend: npx vitest run test/workspace-registry.test.ts # 1 file passed, 23 tests passed backend: npx tsc --noEmit -p . # exit 0 frontend: npx vitest run # 51 files passed, 398 tests passed frontend: npx tsc -b # exit 0 ``` The full backend `npx vitest run` was also attempted after allowing its local SSE test socket. The Task 10 registry tests passed, but seven unchanged SSE/session tests fail because their default, unbootstrapped registry makes session authorization return the intentional `session storage is unavailable` response. This failure is outside the Task 10 diff; it persists without any changed Task 10 route or test-harness code.