Cross-tab workspace conflict detection and stale-write protection
维护者通常 1 天内回复
还没有人认领这个 Issue。
评估
- 难度
- 5/5
- 预计耗时
- 一周以上
- 新手友好度
- 38/100
- Issue 类型
- 功能
- 描述清晰度
- 基本清楚
- 活跃度
- 冷清
- 技术栈
- typescript
调研方向
首先定位 issue 中提到的 workspace repository 提交和删除路径、IndexedDB 事务边界,以及现有的 stale-wave/session guards。然后追踪 BroadcastChannel 的连接点和 workspace session UI 的行为。当列出的 repository、通知、冲突、删除、fallback 和跨 workspace 测试全部通过,且不会静默覆盖更新的状态或丢弃本地草稿时,即表示完成。
由索引模型根据 Issue 内容生成。
描述
Goal
Prevent silent lost updates when the same workspace is open for editing in multiple browser tabs.
This is a follow-up to:
- #406 — multi-workspace persistence;
- #407 — unified Workbench/Dashboard routing;
- #408 — workspace selector and management UI.
Those issues may initially retain documented last-write-wins behavior, but this issue defines the required safer end state.
Problem
Workspace commits replace the complete workspace aggregate. If two tabs load revision N, then make independent changes, the later successful write can silently erase the earlier tab's committed changes.
Examples:
- Workbench tab edits/saves a query while another tab reorders Dashboard tiles.
- Two Workbench tabs edit different saved queries in the same workspace.
- One tab deletes or renames a workspace while another continues editing it.
The atomic whole-record write prevents partial corruption, but it does not prevent stale overwrites.
Product decisions
- One browser tab may safely view a workspace while another edits it.
- Multiple editable tabs are allowed, but stale commits must not overwrite newer state silently.
- The application should notify open tabs promptly when another tab commits or deletes the same workspace.
- Conflict handling must preserve the user's local draft for review/retry.
- Automatic field-level merging is not required initially.
Revision/generation contract
Add a workspace-level persistence generation suitable for optimistic concurrency.
Equivalent model:
interface StoredWorkspace {
storageVersion: 2;
id: string;
key: string;
name: string;
generation: number;
queries: SavedQueryV2[];
dashboard: DashboardDocumentV1 | null;
}
Rules:
- New workspace starts at generation 1.
- Every successful workspace mutation increments generation exactly once.
- Validation, reads, previews, exports, and failed writes do not increment it.
- The repository commit accepts the generation the caller loaded.
- A commit succeeds only if the stored generation still matches the caller's expected generation.
- On success, persist and return the next generation atomically.
- On mismatch, return a typed stale/conflict result and do not write.
The exact storage implementation may use an IndexedDB readwrite transaction containing get/check/put.
Repository API
Equivalent contract:
type WorkspaceCommitResult =
| { ok: true; workspace: StoredWorkspace }
| { ok: false; kind: 'stale'; current: StoredWorkspace }
| { ok: false; kind: 'not-found' }
| { ok: false; kind: 'invalid' | 'persistence'; diagnostics: WorkspaceDiagnostic[] };
commit(candidate: StoredWorkspace, expectedGeneration: number): Promise<WorkspaceCommitResult>;
Requirements:
- Generation comparison and write occur in one transaction.
- Stale candidates never reach storage.
- The returned current snapshot is validated before use, or a separate reload path is provided.
- Delete should optionally accept expected generation so a stale management dialog cannot delete a workspace that changed after confirmation was opened.
Cross-tab notifications
Use BroadcastChannel where available, with an injected/testable seam.
Suggested messages:
type WorkspaceBroadcast =
| { kind: 'committed'; workspaceId: string; key: string; generation: number; sourceTabId: string }
| { kind: 'deleted'; workspaceId: string; key: string; sourceTabId: string }
| { kind: 'renamed'; workspaceId: string; key: string; name: string; generation: number; sourceTabId: string };
Requirements:
- Generate a per-tab source ID and ignore own messages.
- Broadcast only after durable persistence succeeds.
- A missed notification must not compromise correctness; generation checks remain authoritative.
- Provide a fallback based on reload/visibility/focus if
BroadcastChannelis unavailable. The fallback need not be instantaneous, but stale commits must still be rejected.
UI behavior
Clean tab receives a newer generation
If the tab has no local dirty drafts or pending workspace mutation:
- reload/project the newest workspace automatically;
- retain the current surface and mode;
- preserve the selected/open saved query where its ID still exists;
- show a subtle notification such as Workspace updated in another tab.
Dirty tab receives a newer generation
Do not overwrite local drafts.
- Mark the workspace session stale.
- Show a persistent warning/banner.
- Disable or intercept commit actions until the conflict is resolved.
- Offer:
- Reload and discard local changes;
- Keep local draft for copy/review;
- Export local draft/workspace where practical.
A simple initial conflict flow may require the user to copy changes manually; field-level merge is not required.
Commit receives stale result
Even if no notification was received:
- keep the local draft unchanged;
- show a clear conflict message, never a generic persistence failure;
- fetch/display the current workspace generation;
- do not retry automatically with last-write-wins semantics.
Workspace deleted elsewhere
- Stop further commits and dashboard execution once observed.
- Render Workspace not found/deleted in another tab.
- Preserve unsaved editor text in memory long enough for copy/export.
- Do not silently navigate to another workspace.
View-only Dashboard tab
- On commit notification, reload the live Dashboard automatically when safe.
- View mode never creates conflicts because it cannot mutate.
Pending async operations
Protect against races where an operation starts on generation N and another tab commits before it finishes.
- Candidate construction must use the current queued mutation snapshot.
- Repository expected-generation check remains final authority.
- On stale result, do not project the failed candidate.
- Query execution results may finish against an older dashboard; existing stale-wave/session guards should prevent incorrect rendering where applicable.
Tests
Add unit/integration coverage for at least:
- Two repositories/tabs load generation 1; first commit succeeds to 2; second commit is rejected stale.
- Failed/stale commit leaves stored workspace unchanged.
- Generation increments exactly once per successful mutation.
- Broadcast occurs only after successful persistence.
- Own broadcasts are ignored.
- Clean receiving tab reloads newer workspace.
- Dirty receiving tab preserves drafts and marks session stale.
- A missed broadcast still results in stale commit rejection.
- Delete notification renders missing/deleted state without fallback.
- View-mode Dashboard reloads after another tab commits.
- Rename notification updates display name without changing workspace key.
- BroadcastChannel-unavailable fallback preserves correctness.
- Concurrent mutations to different workspaces do not conflict.
- Stale delete confirmation cannot delete a newer generation when expected-generation deletion is enabled.
Acceptance criteria
- A stale browser tab cannot silently overwrite a newer workspace commit.
- Users receive a distinct conflict state and retain local drafts.
- Clean tabs update when another tab commits the same workspace.
- Deletion in another tab is handled explicitly without fallback substitution.
- Different workspaces remain independently editable across tabs.
- Correctness does not depend solely on BroadcastChannel delivery.
Non-goals
- Automatic SQL/spec/dashboard three-way merge.
- Collaborative real-time editing.
- Backend locking or distributed synchronization.
- Cross-device conflict handling.
- Revision history or undo across committed generations.
- 主要语言
- TypeScript
- 星标
- 8
- 派生
- 2
- 平均合并
- 1 小时 17 分钟
- 30 天内合并 PR
- 3
环境准备
- 提供 Dockerfile 或 Docker Compose 文件
- 有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
Altinity/altinity-sql-browser 的其他 Issue
-
inbox
难度 2/5 1-3 小时 新手友好度 76/100
Altinity/altinity-sql-browser#605 ·
维护者通常 1 天内回复
-
inbox
难度 2/5 1-3 小时 新手友好度 78/100
Altinity/altinity-sql-browser#509 ·
维护者通常 1 天内回复
-
inbox
难度 2/5 1-3 小时 新手友好度 78/100
Altinity/altinity-sql-browser#489 ·
维护者通常 1 天内回复
-
flamegraph未关闭enhancement
难度 5/5 一周以上 新手友好度 25/100
Altinity/altinity-sql-browser#684 ·
维护者通常 1 天内回复
-
bug
难度 4/5 3-5 天 新手友好度 68/100
Altinity/altinity-sql-browser#680 · 2 条评论 ·
维护者通常 1 天内回复
查看 Altinity/altinity-sql-browser 的全部 Issue
相似的 Issue
-
bug HemiStake
难度 2/5 1-3 小时 新手友好度 68/100
hemilabs/ui-monorepo#2413 ·
维护者通常 1 天内回复
-
component/ui framework/react kind/bug language/javascript
难度 2/5 1-3 小时 新手友好度 78/100
meshery/meshery#22216 · 3 条评论 ·
维护者通常 1 天内回复
-
type/bug
难度 2/5 1-3 小时 新手友好度 72/100
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 84/100
paperclipai/paperclip#14982 ·
维护者通常 1 天内回复
-
community first-timers-only good first issue hacktoberfest help wanted low hanging fruit up-for-grabs
难度 1/5 1 小时以内 新手友好度 95/100
lingdojo/kana-dojo#31515 · 1 条评论 · 5 个 reaction ·
维护者通常 1 天内回复