@ai-matrx/office 0.8.35 → 0.8.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/aidream/aidream/apps/shared/office/dist/sheets-comment-anchors.cjs","../src/sheets/comment-anchors.ts","../src/collab/sheets/transform/axis.ts","../src/collab/sheets/comment-anchor-transform.ts"],"names":["next"],"mappings":"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","file":"/home/runner/work/aidream/aidream/apps/shared/office/dist/sheets-comment-anchors.cjs","sourcesContent":[null,"/**\n * Cell comment anchors in a workbook (COLLAB-DESIGN §8, unit C9).\n *\n * The anchor is a workbook resource (`SHEET_MATRX_COMMENT_ANCHORS_PLUGIN`): per sheet, anchor id → cell. It is\n * changed only by `matrx.mutation.set-comment-anchor`, which travels in the ordered room like every covered\n * mutation (its transform is `collab/sheets/comment-anchor-transform.ts`), and it follows every row/column\n * insert, remove and move this seat applies — its own, a peer's, an undo — through `moveSheetAnchorCell`, the\n * same function the transform uses, so every seat holds the same cells. A removed row, column or sheet takes the\n * anchor out: the thread in `platform.comments` (part_anchor `anchor:<id>`) is then orphaned, never dropped.\n *\n * `installSheetCommentAnchors(univer)` must run before the workbook unit is created (the resource loads with it).\n */\nimport { CommandType, ICommandService, IResourceManagerService, UndoCommandId, UniverInstanceType } from \"@univerjs/core\";\n\nimport { SET_SHEET_COMMENT_ANCHOR, moveSheetAnchorCell, structuralAxisOp, type SheetAnchorCell } from \"../collab/sheets/comment-anchor-transform\";\n\nexport { SET_SHEET_COMMENT_ANCHOR, sheetCellName, type SheetAnchorCell } from \"../collab/sheets/comment-anchor-transform\";\n\nexport const SHEET_COMMENT_ANCHORS_RESOURCE = \"SHEET_MATRX_COMMENT_ANCHORS_PLUGIN\" as const;\n\n/** Per sheet id: anchor id → cell. */\nexport type SheetAnchors = Record<string, Record<string, SheetAnchorCell>>;\n\nexport interface SetSheetCommentAnchorParams {\n unitId: string;\n subUnitId: string;\n anchorId: string;\n /** null takes the anchor out. */\n cell: SheetAnchorCell | null;\n}\n\n/** Apply one mutation to a workbook's anchors (pure; returns the same object when nothing changed). */\nexport function applyToSheetAnchors(anchors: SheetAnchors, id: string, params: Readonly<Record<string, unknown>>): SheetAnchors {\n if (id === SET_SHEET_COMMENT_ANCHOR) {\n const p = params as unknown as SetSheetCommentAnchorParams;\n const sheet = { ...(anchors[p.subUnitId] ?? {}) };\n if (p.cell) sheet[p.anchorId] = { row: p.cell.row, col: p.cell.col };\n else if (sheet[p.anchorId]) delete sheet[p.anchorId];\n else return anchors;\n const next = { ...anchors, [p.subUnitId]: sheet };\n if (Object.keys(sheet).length === 0) delete next[p.subUnitId];\n return next;\n }\n const sheetId = String(params.subUnitId ?? \"\");\n const sheet = anchors[sheetId];\n if (!sheet) return anchors;\n let changed = false;\n const moved: Record<string, SheetAnchorCell> = {};\n for (const [anchorId, cell] of Object.entries(sheet)) {\n const to = moveSheetAnchorCell(cell, sheetId, id, params);\n if (to !== cell) changed = true;\n if (to) moved[anchorId] = to;\n }\n if (!changed) return anchors;\n const next = { ...anchors, [sheetId]: moved };\n if (Object.keys(moved).length === 0) delete next[sheetId];\n return next;\n}\n\n/** The resource's stored form: keys sorted so every seat writes the same bytes (the state hash reads them). */\nexport function sheetAnchorsToJson(anchors: SheetAnchors | undefined): string {\n if (!anchors) return \"\";\n const sheets = Object.keys(anchors).filter((s) => Object.keys(anchors[s] ?? {}).length > 0).sort();\n if (sheets.length === 0) return \"\";\n const out: Record<string, Record<string, SheetAnchorCell>> = {};\n for (const s of sheets) {\n const ids = Object.keys(anchors[s]!).sort();\n out[s] = Object.fromEntries(ids.map((id) => [id, { row: anchors[s]![id]!.row, col: anchors[s]![id]!.col }]));\n }\n return JSON.stringify(out);\n}\n\nexport function sheetAnchorsFromJson(bytes: string | null | undefined): SheetAnchors {\n if (!bytes) return {};\n try {\n const v = JSON.parse(bytes) as unknown;\n if (!v || typeof v !== \"object\") return {};\n const out: SheetAnchors = {};\n for (const [sheet, ids] of Object.entries(v as Record<string, unknown>)) {\n if (!ids || typeof ids !== \"object\") continue;\n for (const [id, c] of Object.entries(ids as Record<string, unknown>)) {\n const cell = c as SheetAnchorCell;\n if (typeof cell?.row === \"number\" && typeof cell?.col === \"number\") (out[sheet] ??= {})[id] = { row: cell.row, col: cell.col };\n }\n }\n return out;\n } catch {\n return {};\n }\n}\n\n/**\n * What a removal took out, kept on the seat that may undo it (Univer's undo stack is per seat). Undoing a row, column or\n * sheet delete re-creates the cells; the anchors that were inside come back with them (the thread is no longer orphaned).\n */\ninterface RemovedAnchors {\n sheetId: string;\n /** null: the whole sheet was removed. */\n axis: \"row\" | \"col\" | null;\n start: number;\n count: number;\n anchors: Array<{ id: string; cell: SheetAnchorCell }>;\n}\nconst REMOVED_KEEP = 25;\n\n/** Anchors in `before` that `after` no longer has, on the sheet a removal touched. */\nfunction removedBy(before: SheetAnchors, after: SheetAnchors, id: string, params: Readonly<Record<string, unknown>>): RemovedAnchors | null {\n const sheetId = String(params.subUnitId ?? \"\");\n const gone = Object.entries(before[sheetId] ?? {})\n .filter(([anchorId]) => !after[sheetId]?.[anchorId])\n .map(([anchorId, cell]) => ({ id: anchorId, cell }));\n if (!gone.length) return null;\n if (id === \"sheet.mutation.remove-sheet\") return { sheetId, axis: null, start: 0, count: 0, anchors: gone };\n const op = structuralAxisOp(id, params);\n if (!op || op.op.kind !== \"remove\") return null;\n return { sheetId, axis: op.axis, start: op.op.start, count: op.op.count, anchors: gone };\n}\n\nexport interface SheetCommentAnchorsHandle {\n /** The anchors of one workbook now. */\n get(unitId: string): SheetAnchors;\n /** Anchor a cell (a new thread) — or take an anchor out with `cell: null` — through the command service. */\n set(params: SetSheetCommentAnchorParams): boolean;\n /** Called after any change to a workbook's anchors (set, moved, removed, loaded). */\n onChange(listener: (unitId: string) => void): () => void;\n dispose(): void;\n}\n\ntype Injector = { get<T>(token: unknown): T };\nconst INSTALLED = Symbol.for(\"ai-matrx.office.sheet-comment-anchors\");\n\n/** Register the anchor mutation, the resource and the structural listener on one Univer (idempotent). */\nexport function installSheetCommentAnchors(univer: unknown): SheetCommentAnchorsHandle {\n const holder = univer as { [INSTALLED]?: SheetCommentAnchorsHandle; __getInjector?: () => Injector };\n if (holder[INSTALLED]) return holder[INSTALLED]!;\n const injector = holder.__getInjector?.();\n if (!injector) throw new Error(\"installSheetCommentAnchors: this Univer exposes no injector\");\n const commands = injector.get<ICommandService>(ICommandService);\n const resources = injector.get<{ registerPluginResource: (hook: unknown) => { dispose(): void } }>(IResourceManagerService);\n\n const byUnit = new Map<string, SheetAnchors>();\n const listeners = new Set<(unitId: string) => void>();\n const changed = (unitId: string) => {\n for (const l of Array.from(listeners)) {\n try {\n l(unitId);\n } catch {\n /* a listener never stops the editor */\n }\n }\n };\n const removed = new Map<string, RemovedAnchors[]>();\n let undoing = 0;\n const apply = (unitId: string, id: string, params: Record<string, unknown>) => {\n const before = byUnit.get(unitId) ?? {};\n const after = applyToSheetAnchors(before, id, params);\n if (after === before) return;\n byUnit.set(unitId, after);\n const gone = removedBy(before, after, id, params);\n if (gone) removed.set(unitId, [...(removed.get(unitId) ?? []), gone].slice(-REMOVED_KEEP));\n changed(unitId);\n };\n /**\n * An undo re-created cells a removal had taken anchors out of: the anchors to put back once the undo is done. They are\n * set AFTER the undo's own mutations (never inside the insert-row's callback): a seat's room log must read\n * `insert-row, then set-anchor`; the other order replays as \"set the anchor at row 4, then insert a row at 4\", which\n * pushes the anchor one row down on every seat that rebuilds from the log (live: B5 came back as B6).\n */\n const toRestore: Array<{ unitId: string; sheetId: string; anchors: Array<{ id: string; cell: SheetAnchorCell }> }> = [];\n const restoreAfterUndo = (unitId: string, id: string, params: Record<string, unknown>) => {\n const list = removed.get(unitId);\n if (!list?.length) return;\n const sheetId = String(params.subUnitId ?? \"\");\n const op = id === \"sheet.mutation.insert-sheet\" ? null : structuralAxisOp(id, params);\n const wholeSheet = id === \"sheet.mutation.insert-sheet\";\n const matches = (r: RemovedAnchors) =>\n wholeSheet\n ? r.axis === null && r.sheetId === String((params.sheet as { id?: string } | undefined)?.id ?? params.subUnitId ?? \"\")\n : !!op && op.op.kind === \"insert\" && r.axis === op.axis && r.sheetId === sheetId && r.start === op.op.at && r.count === op.op.count;\n let at = -1;\n for (let i = list.length - 1; i >= 0 && at < 0; i--) if (matches(list[i]!)) at = i;\n if (at < 0) return;\n const [hit] = list.splice(at, 1);\n toRestore.push({ unitId, sheetId: hit!.sheetId, anchors: hit!.anchors });\n };\n const flushRestores = () => {\n for (const r of toRestore.splice(0)) {\n for (const a of r.anchors) {\n if ((byUnit.get(r.unitId) ?? {})[r.sheetId]?.[a.id]) continue;\n commands.syncExecuteCommand(SET_SHEET_COMMENT_ANCHOR, { unitId: r.unitId, subUnitId: r.sheetId, anchorId: a.id, cell: a.cell });\n }\n }\n };\n\n const disposables: Array<{ dispose(): void }> = [];\n disposables.push(\n commands.registerCommand({\n id: SET_SHEET_COMMENT_ANCHOR,\n type: CommandType.MUTATION,\n handler: (_accessor: unknown, params?: SetSheetCommentAnchorParams) => {\n if (!params?.unitId || !params.subUnitId || !params.anchorId) return false;\n apply(params.unitId, SET_SHEET_COMMENT_ANCHOR, params as unknown as Record<string, unknown>);\n return true;\n },\n } as never),\n );\n disposables.push(\n resources.registerPluginResource({\n pluginName: SHEET_COMMENT_ANCHORS_RESOURCE,\n businesses: [UniverInstanceType.UNIVER_SHEET],\n toJson: (unitId: string) => sheetAnchorsToJson(byUnit.get(unitId)),\n parseJson: (bytes: string) => sheetAnchorsFromJson(bytes),\n onLoad: (unitId: string, value: SheetAnchors) => {\n byUnit.set(unitId, value ?? {});\n changed(unitId);\n },\n onUnLoad: (unitId: string) => {\n byUnit.delete(unitId);\n changed(unitId);\n },\n }),\n );\n disposables.push(\n commands.onCommandExecuted((info: { id: string; type?: CommandType; params?: unknown }) => {\n if (info.type !== CommandType.MUTATION || info.id === SET_SHEET_COMMENT_ANCHOR) return;\n const p = info.params as Record<string, unknown> | undefined;\n const unitId = typeof p?.unitId === \"string\" ? p.unitId : null;\n if (!unitId || !byUnit.has(unitId)) return;\n apply(unitId, info.id, p!);\n if (undoing > 0) restoreAfterUndo(unitId, info.id, p!);\n }),\n );\n disposables.push(\n commands.beforeCommandExecuted((info: { id: string }) => {\n if (info.id === UndoCommandId) undoing++;\n }),\n );\n disposables.push(\n commands.onCommandExecuted((info: { id: string }) => {\n if (info.id === UndoCommandId && undoing > 0) {\n undoing--;\n if (undoing === 0) flushRestores();\n }\n }),\n );\n\n const handle: SheetCommentAnchorsHandle = {\n get: (unitId) => byUnit.get(unitId) ?? {},\n set: (params) => commands.syncExecuteCommand(SET_SHEET_COMMENT_ANCHOR, params) === true,\n onChange: (l) => {\n listeners.add(l);\n return () => listeners.delete(l);\n },\n dispose: () => {\n for (const d of disposables.splice(0)) d.dispose();\n listeners.clear();\n delete holder[INSTALLED];\n },\n };\n holder[INSTALLED] = handle;\n return handle;\n}\n","/**\n * One-dimensional structural algebra for rows and columns: insert, remove and move, the index map each\n * induces, and S-over-S transforms. Index arithmetic is ours (not Univer's ref-range helpers) because ties\n * need the side rule and overlapping removes must subtract, which no free helper does.\n *\n * Semantics pinned to Univer 1.0.4:\n * - insert(at, n): new rows occupy [at, at+n); old row i >= at moves to i+n (the row AT `at` moves down).\n * - remove(start, n): rows [start, start+n) go; i >= start+n moves to i-n.\n * - move(from, n, to): `moveMatrixArray` — rows [from, from+n) are placed before old row `to`. Forward\n * (to >= from+n): block lands at [to-n, to). Backward (to < from): block lands at [to, to+n). A `to` in\n * [from, from+n] moves nothing (Univer throws for [from, from+n)); we treat it as a no-op.\n */\nimport type { Side } from \"./types\";\n\nexport type Axis = \"row\" | \"col\";\n\nexport type AxisOp =\n | { kind: \"insert\"; at: number; count: number }\n | { kind: \"remove\"; start: number; count: number }\n | { kind: \"move\"; from: number; count: number; to: number };\n\nexport function isNoopMove(op: { from: number; count: number; to: number }): boolean {\n return op.count <= 0 || (op.to >= op.from && op.to <= op.from + op.count);\n}\n\n/** Where the moved block starts after the move. */\nexport function movedBlockStart(op: { from: number; count: number; to: number }): number {\n return op.to > op.from ? op.to - op.count : op.to;\n}\n\n/** Old index → new index, or null when the index is removed. */\nexport function mapIndex(op: AxisOp, i: number): number | null {\n switch (op.kind) {\n case \"insert\":\n return i >= op.at ? i + op.count : i;\n case \"remove\":\n if (i < op.start) return i;\n if (i < op.start + op.count) return null;\n return i - op.count;\n case \"move\": {\n if (isNoopMove(op)) return i;\n const { from, count, to } = op;\n if (i >= from && i < from + count) return movedBlockStart(op) + (i - from);\n if (to > from) return i >= from + count && i < to ? i - count : i;\n return i >= to && i < from ? i + count : i;\n }\n }\n}\n\n/** Contiguous runs of a sorted, de-duplicated index list. */\nexport function runs(indices: Iterable<number>): Array<{ start: number; count: number }> {\n const sorted = [...new Set(indices)].sort((a, b) => a - b);\n const out: Array<{ start: number; count: number }> = [];\n for (const i of sorted) {\n const last = out[out.length - 1];\n if (last && last.start + last.count === i) last.count++;\n else out.push({ start: i, count: 1 });\n }\n return out;\n}\n\n/**\n * A gap (insert position: \"before old row p\") through a move. Gaps strictly inside the block ride with\n * it; any other gap is a gap of the non-block rows. At the move's own target gap an insert lands BEFORE\n * the block (side-independent; `transformAxis(move, insert)` keeps the same order).\n */\nfunction mapGapThroughMove(p: number, mv: { from: number; count: number; to: number }): number {\n const { from, count, to } = mv;\n if (p > from && p < from + count) return movedBlockStart(mv) + (p - from);\n const g = p <= from ? p : p - count; // gap index among the non-block rows\n const tg = to <= from ? to : to - count;\n return g <= tg ? g : g + count;\n}\n\n/**\n * x over y on ONE axis. Returns the list of axis ops that apply after y (empty = x vanished), or null when\n * the pair has no transform (the caller turns that into a barrier).\n */\nexport function transformAxis(x: AxisOp, y: AxisOp, side: Side): AxisOp[] | null {\n if (y.kind === \"move\" && isNoopMove(y)) return [x];\n if (x.kind === \"move\" && isNoopMove(x)) return [];\n switch (x.kind) {\n case \"insert\": {\n if (y.kind === \"insert\") {\n if (x.at < y.at || (x.at === y.at && side === \"left\")) return [x];\n return [{ ...x, at: x.at + y.count }];\n }\n if (y.kind === \"remove\") {\n if (x.at <= y.start) return [x];\n if (x.at >= y.start + y.count) return [{ ...x, at: x.at - y.count }];\n return [{ ...x, at: y.start }]; // an insert inside a removed span survives at its start\n }\n return [{ ...x, at: mapGapThroughMove(x.at, y) }];\n }\n case \"remove\": {\n const kept: number[] = [];\n for (let i = x.start; i < x.start + x.count; i++) {\n const m = mapIndex(y, i);\n if (m !== null) kept.push(m);\n }\n if (y.kind === \"insert\" || y.kind === \"remove\" || y.kind === \"move\") {\n // Removes are emitted highest first so each index is still valid when it applies.\n return runs(kept)\n .reverse()\n .map((r) => ({ kind: \"remove\" as const, start: r.start, count: r.count }));\n }\n return null;\n }\n case \"move\": {\n if (y.kind === \"insert\") {\n const inside = y.at > x.from && y.at < x.from + x.count;\n const from = inside || y.at > x.from ? x.from : x.from + y.count;\n const count = inside ? x.count + y.count : x.count;\n const to = x.to >= y.at ? x.to + y.count : x.to;\n const out = { kind: \"move\" as const, from, count, to };\n return isNoopMove(out) ? [] : [out];\n }\n if (y.kind === \"remove\") {\n const kept: number[] = [];\n for (let i = x.from; i < x.from + x.count; i++) {\n const m = mapIndex(y, i);\n if (m !== null) kept.push(m);\n }\n if (kept.length === 0) return [];\n const r = runs(kept);\n if (r.length !== 1) return null;\n const to = x.to <= y.start ? x.to : x.to >= y.start + y.count ? x.to - y.count : y.start;\n const out = { kind: \"move\" as const, from: r[0]!.start, count: r[0]!.count, to };\n return isNoopMove(out) ? [] : [out];\n }\n // move over move. The same rows moved twice: the later move wins, the earlier one is dropped.\n // Blocks that only partly overlap have no transform yet.\n const overlap = Math.min(x.from + x.count, y.from + y.count) - Math.max(x.from, y.from);\n if (overlap > 0) {\n if (x.from !== y.from || x.count !== y.count) return null;\n if (side === \"right\") return [];\n const out = { kind: \"move\" as const, from: movedBlockStart(y), count: x.count, to: mapGapThroughMove(x.to, y) };\n return isNoopMove(out) ? [] : [out];\n }\n // Disjoint blocks: x's rows through y must stay contiguous; the target gap goes through y.\n const mapped: number[] = [];\n for (let i = x.from; i < x.from + x.count; i++) mapped.push(mapIndex(y, i)!);\n const r = runs(mapped);\n if (r.length !== 1) return null;\n let to: number;\n if (x.to > y.from && x.to < y.from + y.count) to = movedBlockStart(y) + (x.to - y.from);\n else to = mapGapThroughMove(x.to, y) + (x.to === y.to && side === \"right\" ? y.count : 0); // same target: later block first\n const out = { kind: \"move\" as const, from: r[0]!.start, count: r[0]!.count, to };\n if (to > out.from && to < out.from + out.count) return null;\n return isNoopMove(out) ? [] : [out];\n }\n }\n}\n","/**\n * The sheet comment-anchor transform (COLLAB-DESIGN §8 + §10 class R, unit C9). Its own file; the sheet\n * transformer calls it first for every pair (`transformMutation`) and the registry declares its mutation id.\n *\n * A cell comment is anchored by `matrx.mutation.set-comment-anchor` `{ subUnitId, anchorId, cell: {row, col} | null }`\n * (null takes the anchor out). The anchor is a ranged resource keyed by id:\n * - anchor over a structural op on its sheet: the cell moves by the same index arithmetic as cells (`mapIndex`);\n * a cell whose row or column was removed DROPS the anchor (its author is told) and the thread is orphaned;\n * - anchor over the removal of its sheet: dropped the same way;\n * - anchor over the same anchor id: the later commit wins (§6.4), the earlier side is told;\n * - anything else over an anchor, and an anchor over anything else: unchanged (anchors never move content).\n * Every seat moves its stored anchors through each structural op it applies with `moveSheetAnchorCell` — the\n * same function — so a seat that set an anchor before a peer's row insert and a seat that received the anchor\n * after it end with the same cell.\n */\nimport { mapIndex, type AxisOp } from \"./transform/axis\";\nimport type { SheetMutation, Side, TransformResult } from \"./transform/types\";\n\nexport const SET_SHEET_COMMENT_ANCHOR = \"matrx.mutation.set-comment-anchor\";\n\n/** Mutation ids this package defines itself (never emitted by Univer; the census lists them apart). */\nexport const OWN_SHEET_MUTATION_IDS: readonly string[] = [SET_SHEET_COMMENT_ANCHOR];\n\n/**\n * A sheet op that only sets or clears comment anchors: every mutation is `matrx.mutation.set-comment-anchor`. A seat\n * that may comment but not edit sends exactly these (the door `content.collab_append` checks the same rule).\n */\nexport function isSheetsAnchorOnlyOp(op: ReadonlyArray<SheetMutation> | null | undefined): boolean {\n return !!op && op.length > 0 && op.every((m) => m.id === SET_SHEET_COMMENT_ANCHOR);\n}\n\nexport interface SheetAnchorCell {\n row: number;\n col: number;\n}\n\nconst REMOVE_SHEET = \"sheet.mutation.remove-sheet\";\n\ntype R = { startRow: number; endRow: number; startColumn: number; endColumn: number };\n\n/** The row/column op a Univer structural mutation makes, from its params (the same params on every seat). */\nexport function structuralAxisOp(id: string, params: Readonly<Record<string, unknown>>): { axis: \"row\" | \"col\"; op: AxisOp } | null {\n const p = params as Record<string, R | undefined>;\n switch (id) {\n case \"sheet.mutation.insert-row\":\n return p.range ? { axis: \"row\", op: { kind: \"insert\", at: p.range.startRow, count: p.range.endRow - p.range.startRow + 1 } } : null;\n case \"sheet.mutation.remove-rows\":\n return p.range ? { axis: \"row\", op: { kind: \"remove\", start: p.range.startRow, count: p.range.endRow - p.range.startRow + 1 } } : null;\n case \"sheet.mutation.insert-col\":\n return p.range ? { axis: \"col\", op: { kind: \"insert\", at: p.range.startColumn, count: p.range.endColumn - p.range.startColumn + 1 } } : null;\n case \"sheet.mutation.remove-col\":\n return p.range ? { axis: \"col\", op: { kind: \"remove\", start: p.range.startColumn, count: p.range.endColumn - p.range.startColumn + 1 } } : null;\n case \"sheet.mutation.move-rows\":\n return p.sourceRange && p.targetRange\n ? { axis: \"row\", op: { kind: \"move\", from: p.sourceRange.startRow, count: p.sourceRange.endRow - p.sourceRange.startRow + 1, to: p.targetRange.startRow } }\n : null;\n case \"sheet.mutation.move-columns\":\n return p.sourceRange && p.targetRange\n ? {\n axis: \"col\",\n op: { kind: \"move\", from: p.sourceRange.startColumn, count: p.sourceRange.endColumn - p.sourceRange.startColumn + 1, to: p.targetRange.startColumn },\n }\n : null;\n default:\n return null;\n }\n}\n\n/**\n * Where an anchored cell is after one applied mutation on `sheet`: the same cell, a moved cell, or null when the\n * mutation removed it (its row, its column or its sheet).\n */\nexport function moveSheetAnchorCell(cell: SheetAnchorCell, sheet: string, id: string, params: Readonly<Record<string, unknown>>): SheetAnchorCell | null {\n if (String(params.subUnitId ?? \"\") !== sheet) return cell;\n if (id === REMOVE_SHEET) return null;\n const s = structuralAxisOp(id, params);\n if (!s) return cell;\n if (s.axis === \"row\") {\n const row = mapIndex(s.op, cell.row);\n return row === null ? null : row === cell.row ? cell : { row, col: cell.col };\n }\n const col = mapIndex(s.op, cell.col);\n return col === null ? null : col === cell.col ? cell : { row: cell.row, col };\n}\n\nfunction colName(c: number): string {\n let s = \"\";\n for (let n = c + 1; n > 0; n = Math.floor((n - 1) / 26)) s = String.fromCharCode(65 + ((n - 1) % 26)) + s;\n return s;\n}\n/** \"B5\" for row 4, column 1. */\nexport const sheetCellName = (cell: SheetAnchorCell) => `${colName(cell.col)}${cell.row + 1}`;\n\nfunction cellOf(m: SheetMutation): SheetAnchorCell | null {\n const c = m.params.cell as SheetAnchorCell | null | undefined;\n return c && typeof c.row === \"number\" && typeof c.col === \"number\" ? c : null;\n}\n\nconst ok = (op: SheetMutation[], lost: string[] = [], dropped: string[] = []): TransformResult => ({ op, lost, dropped });\nconst label = (m: SheetMutation) => {\n const c = cellOf(m);\n return `${String(m.params.subUnitId ?? \"\")}!${c ? sheetCellName(c) : \"?\"} comment`;\n};\n\n/** x over y when either is an anchor mutation; null when neither is (the sheet transformer carries on). */\nexport function transformCommentAnchor(x: SheetMutation, y: SheetMutation, side: Side): TransformResult | null {\n const xa = x.id === SET_SHEET_COMMENT_ANCHOR;\n const ya = y.id === SET_SHEET_COMMENT_ANCHOR;\n if (!xa && !ya) return null;\n if (!xa) return ok([x]); // an anchor never moves content\n if (ya) {\n // anchors are keyed by sheet and id: the same id on another sheet is another anchor\n if (x.params.anchorId !== y.params.anchorId || x.params.subUnitId !== y.params.subUnitId) return ok([x]);\n return side === \"left\" ? ok([x], [label(y)]) : ok([], [], [label(x)]);\n }\n const cell = cellOf(x);\n if (!cell) return ok([x]); // taking an anchor out commutes with every move\n const moved = moveSheetAnchorCell(cell, String(x.params.subUnitId ?? \"\"), y.id, y.params);\n if (!moved) return ok([], [], [label(x)]);\n return ok([moved === cell ? x : { id: x.id, params: { ...x.params, cell: moved } }]);\n}\n"]}
1
+ {"version":3,"sources":["/home/runner/work/aidream/aidream/apps/shared/office/dist/sheets-comment-anchors.cjs","../src/sheets/comment-anchors.ts","../src/collab/sheets/transform/axis.ts","../src/collab/sheets/comment-anchor-transform.ts"],"names":["next"],"mappings":"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","file":"/home/runner/work/aidream/aidream/apps/shared/office/dist/sheets-comment-anchors.cjs","sourcesContent":[null,"/**\n * Cell comment anchors in a workbook (COLLAB-DESIGN §8, unit C9).\n *\n * The anchor is a workbook resource (`SHEET_MATRX_COMMENT_ANCHORS_PLUGIN`): per sheet, anchor id → cell. It is\n * changed only by `matrx.mutation.set-comment-anchor`, which travels in the ordered room like every covered\n * mutation (its transform is `collab/sheets/comment-anchor-transform.ts`), and it follows every row/column\n * insert, remove and move this seat applies — its own, a peer's, an undo — through `moveSheetAnchorCell`, the\n * same function the transform uses, so every seat holds the same cells. A removed row, column or sheet takes the\n * anchor out: the thread in `platform.comments` (part_anchor `anchor:<id>`) is then orphaned, never dropped.\n *\n * `installSheetCommentAnchors(univer)` must run before the workbook unit is created (the resource loads with it).\n */\nimport { CommandType, ICommandService, IResourceManagerService, UndoCommandId, UniverInstanceType } from \"@univerjs/core\";\n\nimport { SET_SHEET_COMMENT_ANCHOR, moveSheetAnchorCell, structuralAxisOp, type SheetAnchorCell } from \"../collab/sheets/comment-anchor-transform\";\n\nexport { SET_SHEET_COMMENT_ANCHOR, sheetCellName, type SheetAnchorCell } from \"../collab/sheets/comment-anchor-transform\";\n\nexport const SHEET_COMMENT_ANCHORS_RESOURCE = \"SHEET_MATRX_COMMENT_ANCHORS_PLUGIN\" as const;\n\n/** Per sheet id: anchor id → cell. */\nexport type SheetAnchors = Record<string, Record<string, SheetAnchorCell>>;\n\nexport interface SetSheetCommentAnchorParams {\n unitId: string;\n subUnitId: string;\n anchorId: string;\n /** null takes the anchor out. */\n cell: SheetAnchorCell | null;\n}\n\n/** Apply one mutation to a workbook's anchors (pure; returns the same object when nothing changed). */\nexport function applyToSheetAnchors(anchors: SheetAnchors, id: string, params: Readonly<Record<string, unknown>>): SheetAnchors {\n if (id === SET_SHEET_COMMENT_ANCHOR) {\n const p = params as unknown as SetSheetCommentAnchorParams;\n const sheet = { ...(anchors[p.subUnitId] ?? {}) };\n if (p.cell) sheet[p.anchorId] = { row: p.cell.row, col: p.cell.col };\n else if (sheet[p.anchorId]) delete sheet[p.anchorId];\n else return anchors;\n const next = { ...anchors, [p.subUnitId]: sheet };\n if (Object.keys(sheet).length === 0) delete next[p.subUnitId];\n return next;\n }\n const sheetId = String(params.subUnitId ?? \"\");\n const sheet = anchors[sheetId];\n if (!sheet) return anchors;\n let changed = false;\n const moved: Record<string, SheetAnchorCell> = {};\n for (const [anchorId, cell] of Object.entries(sheet)) {\n const to = moveSheetAnchorCell(cell, sheetId, id, params);\n if (to !== cell) changed = true;\n if (to) moved[anchorId] = to;\n }\n if (!changed) return anchors;\n const next = { ...anchors, [sheetId]: moved };\n if (Object.keys(moved).length === 0) delete next[sheetId];\n return next;\n}\n\n/** The resource's stored form: keys sorted so every seat writes the same bytes (the state hash reads them). */\nexport function sheetAnchorsToJson(anchors: SheetAnchors | undefined): string {\n if (!anchors) return \"\";\n const sheets = Object.keys(anchors).filter((s) => Object.keys(anchors[s] ?? {}).length > 0).sort();\n if (sheets.length === 0) return \"\";\n const out: Record<string, Record<string, SheetAnchorCell>> = {};\n for (const s of sheets) {\n const ids = Object.keys(anchors[s]!).sort();\n out[s] = Object.fromEntries(ids.map((id) => [id, { row: anchors[s]![id]!.row, col: anchors[s]![id]!.col }]));\n }\n return JSON.stringify(out);\n}\n\nexport function sheetAnchorsFromJson(bytes: string | null | undefined): SheetAnchors {\n if (!bytes) return {};\n try {\n const v = JSON.parse(bytes) as unknown;\n if (!v || typeof v !== \"object\") return {};\n const out: SheetAnchors = {};\n for (const [sheet, ids] of Object.entries(v as Record<string, unknown>)) {\n if (!ids || typeof ids !== \"object\") continue;\n for (const [id, c] of Object.entries(ids as Record<string, unknown>)) {\n const cell = c as SheetAnchorCell;\n if (typeof cell?.row === \"number\" && typeof cell?.col === \"number\") (out[sheet] ??= {})[id] = { row: cell.row, col: cell.col };\n }\n }\n return out;\n } catch {\n return {};\n }\n}\n\n/**\n * What a removal took out, kept on the seat that may undo it (Univer's undo stack is per seat). Undoing a row, column or\n * sheet delete re-creates the cells; the anchors that were inside come back with them (the thread is no longer orphaned).\n */\ninterface RemovedAnchors {\n sheetId: string;\n /** null: the whole sheet was removed. */\n axis: \"row\" | \"col\" | null;\n start: number;\n count: number;\n anchors: Array<{ id: string; cell: SheetAnchorCell }>;\n}\nconst REMOVED_KEEP = 25;\n\n/** Anchors in `before` that `after` no longer has, on the sheet a removal touched. */\nfunction removedBy(before: SheetAnchors, after: SheetAnchors, id: string, params: Readonly<Record<string, unknown>>): RemovedAnchors | null {\n const sheetId = String(params.subUnitId ?? \"\");\n const gone = Object.entries(before[sheetId] ?? {})\n .filter(([anchorId]) => !after[sheetId]?.[anchorId])\n .map(([anchorId, cell]) => ({ id: anchorId, cell }));\n if (!gone.length) return null;\n if (id === \"sheet.mutation.remove-sheet\") return { sheetId, axis: null, start: 0, count: 0, anchors: gone };\n const op = structuralAxisOp(id, params);\n if (!op || op.op.kind !== \"remove\") return null;\n return { sheetId, axis: op.axis, start: op.op.start, count: op.op.count, anchors: gone };\n}\n\nexport interface SheetCommentAnchorsHandle {\n /** The anchors of one workbook now. */\n get(unitId: string): SheetAnchors;\n /** Anchor a cell (a new thread) — or take an anchor out with `cell: null` — through the command service. */\n set(params: SetSheetCommentAnchorParams): boolean;\n /** Called after any change to a workbook's anchors (set, moved, removed, loaded). */\n onChange(listener: (unitId: string) => void): () => void;\n dispose(): void;\n}\n\ntype Injector = { get<T>(token: unknown): T };\nconst INSTALLED = Symbol.for(\"ai-matrx.office.sheet-comment-anchors\");\n\n/** Register the anchor mutation, the resource and the structural listener on one Univer (idempotent). */\nexport function installSheetCommentAnchors(univer: unknown): SheetCommentAnchorsHandle {\n const holder = univer as { [INSTALLED]?: SheetCommentAnchorsHandle; __getInjector?: () => Injector };\n if (holder[INSTALLED]) return holder[INSTALLED]!;\n const injector = holder.__getInjector?.();\n if (!injector) throw new Error(\"installSheetCommentAnchors: this Univer exposes no injector\");\n const commands = injector.get<ICommandService>(ICommandService);\n const resources = injector.get<{ registerPluginResource: (hook: unknown) => { dispose(): void } }>(IResourceManagerService);\n\n const byUnit = new Map<string, SheetAnchors>();\n const listeners = new Set<(unitId: string) => void>();\n const changed = (unitId: string) => {\n for (const l of Array.from(listeners)) {\n try {\n l(unitId);\n } catch {\n /* a listener never stops the editor */\n }\n }\n };\n const removed = new Map<string, RemovedAnchors[]>();\n let undoing = 0;\n const apply = (unitId: string, id: string, params: Record<string, unknown>) => {\n const before = byUnit.get(unitId) ?? {};\n const after = applyToSheetAnchors(before, id, params);\n if (after === before) return;\n byUnit.set(unitId, after);\n const gone = removedBy(before, after, id, params);\n if (gone) removed.set(unitId, [...(removed.get(unitId) ?? []), gone].slice(-REMOVED_KEEP));\n changed(unitId);\n };\n /**\n * An undo re-created cells a removal had taken anchors out of: the anchors to put back once the undo is done. They are\n * set AFTER the undo's own mutations (never inside the insert-row's callback): a seat's room log must read\n * `insert-row, then set-anchor`; the other order replays as \"set the anchor at row 4, then insert a row at 4\", which\n * pushes the anchor one row down on every seat that rebuilds from the log (live: B5 came back as B6).\n */\n const toRestore: Array<{ unitId: string; sheetId: string; anchors: Array<{ id: string; cell: SheetAnchorCell }> }> = [];\n const restoreAfterUndo = (unitId: string, id: string, params: Record<string, unknown>) => {\n const list = removed.get(unitId);\n if (!list?.length) return;\n const sheetId = String(params.subUnitId ?? \"\");\n const op = id === \"sheet.mutation.insert-sheet\" ? null : structuralAxisOp(id, params);\n const wholeSheet = id === \"sheet.mutation.insert-sheet\";\n const matches = (r: RemovedAnchors) =>\n wholeSheet\n ? r.axis === null && r.sheetId === String((params.sheet as { id?: string } | undefined)?.id ?? params.subUnitId ?? \"\")\n : !!op && op.op.kind === \"insert\" && r.axis === op.axis && r.sheetId === sheetId && r.start === op.op.at && r.count === op.op.count;\n let at = -1;\n for (let i = list.length - 1; i >= 0 && at < 0; i--) if (matches(list[i]!)) at = i;\n if (at < 0) return;\n const [hit] = list.splice(at, 1);\n toRestore.push({ unitId, sheetId: hit!.sheetId, anchors: hit!.anchors });\n };\n const flushRestores = () => {\n for (const r of toRestore.splice(0)) {\n for (const a of r.anchors) {\n if ((byUnit.get(r.unitId) ?? {})[r.sheetId]?.[a.id]) continue;\n commands.syncExecuteCommand(SET_SHEET_COMMENT_ANCHOR, { unitId: r.unitId, subUnitId: r.sheetId, anchorId: a.id, cell: a.cell });\n }\n }\n };\n\n const disposables: Array<{ dispose(): void }> = [];\n disposables.push(\n commands.registerCommand({\n id: SET_SHEET_COMMENT_ANCHOR,\n type: CommandType.MUTATION,\n handler: (_accessor: unknown, params?: SetSheetCommentAnchorParams) => {\n if (!params?.unitId || !params.subUnitId || !params.anchorId) return false;\n apply(params.unitId, SET_SHEET_COMMENT_ANCHOR, params as unknown as Record<string, unknown>);\n return true;\n },\n } as never),\n );\n disposables.push(\n resources.registerPluginResource({\n pluginName: SHEET_COMMENT_ANCHORS_RESOURCE,\n businesses: [UniverInstanceType.UNIVER_SHEET],\n toJson: (unitId: string) => sheetAnchorsToJson(byUnit.get(unitId)),\n parseJson: (bytes: string) => sheetAnchorsFromJson(bytes),\n onLoad: (unitId: string, value: SheetAnchors) => {\n byUnit.set(unitId, value ?? {});\n changed(unitId);\n },\n onUnLoad: (unitId: string) => {\n byUnit.delete(unitId);\n changed(unitId);\n },\n }),\n );\n disposables.push(\n commands.onCommandExecuted((info: { id: string; type?: CommandType; params?: unknown }) => {\n if (info.type !== CommandType.MUTATION || info.id === SET_SHEET_COMMENT_ANCHOR) return;\n const p = info.params as Record<string, unknown> | undefined;\n const unitId = typeof p?.unitId === \"string\" ? p.unitId : null;\n if (!unitId || !byUnit.has(unitId)) return;\n apply(unitId, info.id, p!);\n if (undoing > 0) restoreAfterUndo(unitId, info.id, p!);\n }),\n );\n disposables.push(\n commands.beforeCommandExecuted((info: { id: string }) => {\n if (info.id === UndoCommandId) undoing++;\n }),\n );\n disposables.push(\n commands.onCommandExecuted((info: { id: string }) => {\n if (info.id === UndoCommandId && undoing > 0) {\n undoing--;\n if (undoing === 0) flushRestores();\n }\n }),\n );\n\n const handle: SheetCommentAnchorsHandle = {\n get: (unitId) => byUnit.get(unitId) ?? {},\n set: (params) => commands.syncExecuteCommand(SET_SHEET_COMMENT_ANCHOR, params) === true,\n onChange: (l) => {\n listeners.add(l);\n return () => listeners.delete(l);\n },\n dispose: () => {\n for (const d of disposables.splice(0)) d.dispose();\n listeners.clear();\n delete holder[INSTALLED];\n },\n };\n holder[INSTALLED] = handle;\n return handle;\n}\n","/**\n * One-dimensional structural algebra for rows and columns: insert, remove and move, the index map each\n * induces, and S-over-S transforms. Index arithmetic is ours (not Univer's ref-range helpers) because ties\n * need the side rule and overlapping removes must subtract, which no free helper does.\n *\n * Semantics pinned to Univer 1.0.4:\n * - insert(at, n): new rows occupy [at, at+n); old row i >= at moves to i+n (the row AT `at` moves down).\n * - remove(start, n): rows [start, start+n) go; i >= start+n moves to i-n.\n * - move(from, n, to): `moveMatrixArray` — rows [from, from+n) are placed before old row `to`. Forward\n * (to >= from+n): block lands at [to-n, to). Backward (to < from): block lands at [to, to+n). A `to` in\n * [from, from+n] moves nothing (Univer throws for [from, from+n)); we treat it as a no-op.\n */\nimport type { Side } from \"./types\";\n\nexport type Axis = \"row\" | \"col\";\n\nexport type AxisOp =\n | { kind: \"insert\"; at: number; count: number }\n | { kind: \"remove\"; start: number; count: number }\n | { kind: \"move\"; from: number; count: number; to: number };\n\nexport function isNoopMove(op: { from: number; count: number; to: number }): boolean {\n return op.count <= 0 || (op.to >= op.from && op.to <= op.from + op.count);\n}\n\n/** Where the moved block starts after the move. */\nexport function movedBlockStart(op: { from: number; count: number; to: number }): number {\n return op.to > op.from ? op.to - op.count : op.to;\n}\n\n/** Old index → new index, or null when the index is removed. */\nexport function mapIndex(op: AxisOp, i: number): number | null {\n switch (op.kind) {\n case \"insert\":\n return i >= op.at ? i + op.count : i;\n case \"remove\":\n if (i < op.start) return i;\n if (i < op.start + op.count) return null;\n return i - op.count;\n case \"move\": {\n if (isNoopMove(op)) return i;\n const { from, count, to } = op;\n if (i >= from && i < from + count) return movedBlockStart(op) + (i - from);\n if (to > from) return i >= from + count && i < to ? i - count : i;\n return i >= to && i < from ? i + count : i;\n }\n }\n}\n\n/** Contiguous runs of a sorted, de-duplicated index list. */\nexport function runs(indices: Iterable<number>): Array<{ start: number; count: number }> {\n const sorted = [...new Set(indices)].sort((a, b) => a - b);\n const out: Array<{ start: number; count: number }> = [];\n for (const i of sorted) {\n const last = out[out.length - 1];\n if (last && last.start + last.count === i) last.count++;\n else out.push({ start: i, count: 1 });\n }\n return out;\n}\n\n/**\n * A gap (insert position: \"before old row p\") through a move. Gaps strictly inside the block ride with\n * it; any other gap is a gap of the non-block rows. At the move's own target gap an insert lands BEFORE\n * the block (side-independent; `transformAxis(move, insert)` keeps the same order).\n */\nfunction mapGapThroughMove(p: number, mv: { from: number; count: number; to: number }): number {\n const { from, count, to } = mv;\n if (p > from && p < from + count) return movedBlockStart(mv) + (p - from);\n const g = p <= from ? p : p - count; // gap index among the non-block rows\n const tg = to <= from ? to : to - count;\n return g <= tg ? g : g + count;\n}\n\n/**\n * x over y on ONE axis. Returns the list of axis ops that apply after y (empty = x vanished), or null when\n * the pair has no transform (the caller turns that into a barrier).\n */\nexport function transformAxis(x: AxisOp, y: AxisOp, side: Side): AxisOp[] | null {\n if (y.kind === \"move\" && isNoopMove(y)) return [x];\n if (x.kind === \"move\" && isNoopMove(x)) return [];\n switch (x.kind) {\n case \"insert\": {\n if (y.kind === \"insert\") {\n if (x.at < y.at || (x.at === y.at && side === \"left\")) return [x];\n return [{ ...x, at: x.at + y.count }];\n }\n if (y.kind === \"remove\") {\n if (x.at <= y.start) return [x];\n if (x.at >= y.start + y.count) return [{ ...x, at: x.at - y.count }];\n return [{ ...x, at: y.start }]; // an insert inside a removed span survives at its start\n }\n return [{ ...x, at: mapGapThroughMove(x.at, y) }];\n }\n case \"remove\": {\n const kept: number[] = [];\n for (let i = x.start; i < x.start + x.count; i++) {\n const m = mapIndex(y, i);\n if (m !== null) kept.push(m);\n }\n if (y.kind === \"insert\" || y.kind === \"remove\" || y.kind === \"move\") {\n // Removes are emitted highest first so each index is still valid when it applies.\n return runs(kept)\n .reverse()\n .map((r) => ({ kind: \"remove\" as const, start: r.start, count: r.count }));\n }\n return null;\n }\n case \"move\": {\n if (y.kind === \"insert\") {\n const inside = y.at > x.from && y.at < x.from + x.count;\n const from = inside || y.at > x.from ? x.from : x.from + y.count;\n const count = inside ? x.count + y.count : x.count;\n const to = x.to >= y.at ? x.to + y.count : x.to;\n const out = { kind: \"move\" as const, from, count, to };\n return isNoopMove(out) ? [] : [out];\n }\n if (y.kind === \"remove\") {\n const kept: number[] = [];\n for (let i = x.from; i < x.from + x.count; i++) {\n const m = mapIndex(y, i);\n if (m !== null) kept.push(m);\n }\n if (kept.length === 0) return [];\n const r = runs(kept);\n if (r.length !== 1) return null;\n const to = x.to <= y.start ? x.to : x.to >= y.start + y.count ? x.to - y.count : y.start;\n const out = { kind: \"move\" as const, from: r[0]!.start, count: r[0]!.count, to };\n return isNoopMove(out) ? [] : [out];\n }\n // move over move. The same rows moved twice: the later move wins, the earlier one is dropped.\n // Blocks that only partly overlap have no transform yet.\n const overlap = Math.min(x.from + x.count, y.from + y.count) - Math.max(x.from, y.from);\n if (overlap > 0) {\n if (x.from !== y.from || x.count !== y.count) return null;\n if (side === \"right\") return [];\n const out = { kind: \"move\" as const, from: movedBlockStart(y), count: x.count, to: mapGapThroughMove(x.to, y) };\n return isNoopMove(out) ? [] : [out];\n }\n // Disjoint blocks: x's rows through y must stay contiguous; the target gap goes through y.\n const mapped: number[] = [];\n for (let i = x.from; i < x.from + x.count; i++) mapped.push(mapIndex(y, i)!);\n const r = runs(mapped);\n if (r.length !== 1) return null;\n let to: number;\n if (x.to > y.from && x.to < y.from + y.count) to = movedBlockStart(y) + (x.to - y.from);\n else to = mapGapThroughMove(x.to, y) + (x.to === y.to && side === \"right\" ? y.count : 0); // same target: later block first\n const out = { kind: \"move\" as const, from: r[0]!.start, count: r[0]!.count, to };\n if (to > out.from && to < out.from + out.count) return null;\n return isNoopMove(out) ? [] : [out];\n }\n }\n}\n","/**\n * The sheet comment-anchor transform (COLLAB-DESIGN §8 + §10 class R, unit C9). Its own file; the sheet\n * transformer calls it first for every pair (`transformMutation`) and the registry declares its mutation id.\n *\n * A cell comment is anchored by `matrx.mutation.set-comment-anchor` `{ subUnitId, anchorId, cell: {row, col} | null }`\n * (null takes the anchor out). The anchor is a ranged resource keyed by id:\n * - anchor over a structural op on its sheet: the cell moves by the same index arithmetic as cells (`mapIndex`);\n * a cell whose row or column was removed DROPS the anchor (its author is told) and the thread is orphaned;\n * - anchor over the removal of its sheet: dropped the same way;\n * - anchor over the same anchor id: the later commit wins (§6.4), the earlier side is told;\n * - anything else over an anchor, and an anchor over anything else: unchanged (anchors never move content).\n * Every seat moves its stored anchors through each structural op it applies with `moveSheetAnchorCell` — the\n * same function — so a seat that set an anchor before a peer's row insert and a seat that received the anchor\n * after it end with the same cell.\n *\n * Univer's cell notes (`@univerjs/sheets-note`, C5f) are the same kind of record — keyed by note id, anchored to one\n * cell, never moving content — and transform here too (`transformCellNote`, called first). Their params name the\n * sheet `sheetId`. Facets: a full write (`update-note`: content and cell), a move (`update-note-position`: the cell)\n * and a remove (`remove-note`).\n * - a note over a structural op on its sheet: its cell moves (`moveSheetAnchorCell`); a write whose cell was\n * removed is dropped (the note went with its row), a move whose target was removed becomes a remove;\n * - a structural op over a note: a raw structural mutation runs no plugin ref-range handler synchronously (each\n * seat's watcher moves notes a microtask later), so the op carries the fixup the note is owed — its move, or\n * its remove — and every seat (the fold included) holds the same cells the moment it applies the op;\n * - the same note: a remove wins; a full write over a move takes the move's target cell; two structural ops'\n * fixups both stand (each target went through the other op); otherwise a person's write or move beats a\n * structural op's derived one, else the later commit wins;\n * - two different notes written to one cell at once, or a note change addressed by cell instead of id against\n * another note change on its sheet: barrier (the resource keeps one note per cell, by insertion order).\n */\nimport { mapIndex, type AxisOp } from \"./transform/axis\";\nimport type { SheetMutation, Side, TransformResult } from \"./transform/types\";\n\nexport const SET_SHEET_COMMENT_ANCHOR = \"matrx.mutation.set-comment-anchor\";\n\n/** Mutation ids this package defines itself (never emitted by Univer; the census lists them apart). */\nexport const OWN_SHEET_MUTATION_IDS: readonly string[] = [SET_SHEET_COMMENT_ANCHOR];\n\n/**\n * A sheet op that only sets or clears comment anchors: every mutation is `matrx.mutation.set-comment-anchor`. A seat\n * that may comment but not edit sends exactly these (the door `content.collab_append` checks the same rule).\n */\nexport function isSheetsAnchorOnlyOp(op: ReadonlyArray<SheetMutation> | null | undefined): boolean {\n return !!op && op.length > 0 && op.every((m) => m.id === SET_SHEET_COMMENT_ANCHOR);\n}\n\nexport interface SheetAnchorCell {\n row: number;\n col: number;\n}\n\nconst REMOVE_SHEET = \"sheet.mutation.remove-sheet\";\n\ntype R = { startRow: number; endRow: number; startColumn: number; endColumn: number };\n\n/** The row/column op a Univer structural mutation makes, from its params (the same params on every seat). */\nexport function structuralAxisOp(id: string, params: Readonly<Record<string, unknown>>): { axis: \"row\" | \"col\"; op: AxisOp } | null {\n const p = params as Record<string, R | undefined>;\n switch (id) {\n case \"sheet.mutation.insert-row\":\n return p.range ? { axis: \"row\", op: { kind: \"insert\", at: p.range.startRow, count: p.range.endRow - p.range.startRow + 1 } } : null;\n case \"sheet.mutation.remove-rows\":\n return p.range ? { axis: \"row\", op: { kind: \"remove\", start: p.range.startRow, count: p.range.endRow - p.range.startRow + 1 } } : null;\n case \"sheet.mutation.insert-col\":\n return p.range ? { axis: \"col\", op: { kind: \"insert\", at: p.range.startColumn, count: p.range.endColumn - p.range.startColumn + 1 } } : null;\n case \"sheet.mutation.remove-col\":\n return p.range ? { axis: \"col\", op: { kind: \"remove\", start: p.range.startColumn, count: p.range.endColumn - p.range.startColumn + 1 } } : null;\n case \"sheet.mutation.move-rows\":\n return p.sourceRange && p.targetRange\n ? { axis: \"row\", op: { kind: \"move\", from: p.sourceRange.startRow, count: p.sourceRange.endRow - p.sourceRange.startRow + 1, to: p.targetRange.startRow } }\n : null;\n case \"sheet.mutation.move-columns\":\n return p.sourceRange && p.targetRange\n ? {\n axis: \"col\",\n op: { kind: \"move\", from: p.sourceRange.startColumn, count: p.sourceRange.endColumn - p.sourceRange.startColumn + 1, to: p.targetRange.startColumn },\n }\n : null;\n default:\n return null;\n }\n}\n\n/**\n * Where an anchored cell is after one applied mutation on `sheet`: the same cell, a moved cell, or null when the\n * mutation removed it (its row, its column or its sheet).\n */\nexport function moveSheetAnchorCell(cell: SheetAnchorCell, sheet: string, id: string, params: Readonly<Record<string, unknown>>): SheetAnchorCell | null {\n if (String(params.subUnitId ?? \"\") !== sheet) return cell;\n if (id === REMOVE_SHEET) return null;\n const s = structuralAxisOp(id, params);\n if (!s) return cell;\n if (s.axis === \"row\") {\n const row = mapIndex(s.op, cell.row);\n return row === null ? null : row === cell.row ? cell : { row, col: cell.col };\n }\n const col = mapIndex(s.op, cell.col);\n return col === null ? null : col === cell.col ? cell : { row: cell.row, col };\n}\n\nfunction colName(c: number): string {\n let s = \"\";\n for (let n = c + 1; n > 0; n = Math.floor((n - 1) / 26)) s = String.fromCharCode(65 + ((n - 1) % 26)) + s;\n return s;\n}\n/** \"B5\" for row 4, column 1. */\nexport const sheetCellName = (cell: SheetAnchorCell) => `${colName(cell.col)}${cell.row + 1}`;\n\nfunction cellOf(m: SheetMutation): SheetAnchorCell | null {\n const c = m.params.cell as SheetAnchorCell | null | undefined;\n return c && typeof c.row === \"number\" && typeof c.col === \"number\" ? c : null;\n}\n\nconst ok = (op: SheetMutation[], lost: string[] = [], dropped: string[] = []): TransformResult => ({ op, lost, dropped });\nconst label = (m: SheetMutation) => {\n const c = cellOf(m);\n return `${String(m.params.subUnitId ?? \"\")}!${c ? sheetCellName(c) : \"?\"} comment`;\n};\n\n// ── Univer cell notes (C5f) ──────────────────────────────────────────────────────────────────────────────────\nexport const UPDATE_NOTE = \"sheet.mutation.update-note\";\nexport const REMOVE_NOTE = \"sheet.mutation.remove-note\";\nexport const UPDATE_NOTE_POSITION = \"sheet.mutation.update-note-position\";\nconst NOTE_IDS = new Set([UPDATE_NOTE, REMOVE_NOTE, UPDATE_NOTE_POSITION]);\nexport const isNoteMutation = (id: string): boolean => NOTE_IDS.has(id);\n\ntype NoteRole = \"write\" | \"move\" | \"remove\";\ninterface NoteRef {\n role: NoteRole;\n sheet: string;\n /** The note id, or null when the op addresses the note by its cell (an undo of a new note removes by cell). */\n id: string | null;\n /** Where the op leaves the note: a write's cell, a move's target; null for a remove. */\n cell: SheetAnchorCell | null;\n}\n\nconst cellAt = (row: unknown, col: unknown): SheetAnchorCell | null => (typeof row === \"number\" && typeof col === \"number\" ? { row, col } : null);\nfunction noteRef(m: SheetMutation): NoteRef {\n const p = m.params as Record<string, any>;\n const sheet = String(p.sheetId ?? \"\");\n if (m.id === UPDATE_NOTE) return { role: \"write\", sheet, id: p.note?.id ? String(p.note.id) : null, cell: cellAt(p.row, p.col) };\n if (m.id === UPDATE_NOTE_POSITION) return { role: \"move\", sheet, id: p.noteId ? String(p.noteId) : null, cell: cellAt(p.newPosition?.row, p.newPosition?.col) };\n return { role: \"remove\", sheet, id: p.noteId ? String(p.noteId) : null, cell: p.noteId ? null : cellAt(p.row, p.col) };\n}\nconst noteDerived = (m: SheetMutation) => m.params.derived === true;\nconst noteLabel = (n: NoteRef) => `${n.sheet}!${n.cell ? sheetCellName(n.cell) : \"?\"} note`;\nconst tellNote = (m: SheetMutation, n: NoteRef) => (noteDerived(m) ? [] : [noteLabel(n)]);\nconst noteBarrier = (reason: string): TransformResult => ({ barrier: true, reason: `note: ${reason}` });\nconst removeNoteOf = (sheet: string, id: string): SheetMutation => ({ id: REMOVE_NOTE, params: { sheetId: sheet, noteId: id, derived: true } });\nconst moveNoteTo = (sheet: string, id: string, cell: SheetAnchorCell): SheetMutation => ({\n id: UPDATE_NOTE_POSITION,\n params: { sheetId: sheet, noteId: id, newPosition: { row: cell.row, col: cell.col }, derived: true },\n});\n/** A mutation's sheet as `moveSheetAnchorCell` reads it (structural ops name `subUnitId`). */\nconst onSheet = (m: SheetMutation, sheet: string) => String(m.params.subUnitId ?? \"\") === sheet;\n\n/** x over y when either is a cell-note mutation; null when neither is. */\nexport function transformCellNote(x: SheetMutation, y: SheetMutation, side: Side): TransformResult | null {\n const xn = isNoteMutation(x.id);\n const yn = isNoteMutation(y.id);\n if (!xn && !yn) return null;\n if (!xn) {\n // x moves content (or not); y is a note. A structural x carries the fixup y's note is owed.\n const n = noteRef(y);\n if (n.role === \"remove\" || !n.cell || !onSheet(x, n.sheet)) return ok([x]);\n if (x.id === REMOVE_SHEET) return n.id ? ok([removeNoteOf(n.sheet, n.id), x]) : ok([x]);\n if (!structuralAxisOp(x.id, x.params)) return ok([x]);\n if (!n.id) return noteBarrier(\"a note without an id against a row or column change\");\n const moved = moveSheetAnchorCell(n.cell, n.sheet, x.id, x.params);\n if (!moved) return ok([x, removeNoteOf(n.sheet, n.id)]);\n return moved === n.cell ? ok([x]) : ok([x, moveNoteTo(n.sheet, n.id, moved)]);\n }\n const n = noteRef(x);\n if (!yn) {\n if (!onSheet(y, n.sheet)) return ok([x]);\n if (y.id === REMOVE_SHEET) return ok([], [], tellNote(x, n));\n if (!structuralAxisOp(y.id, y.params)) return ok([x]); // a note never conflicts with content\n if (n.role === \"write\" && !n.id) return noteBarrier(\"a note without an id against a row or column change\");\n const p = x.params as Record<string, any>;\n const from = cellAt(p.row, p.col);\n const fromMoved = from ? moveSheetAnchorCell(from, n.sheet, y.id, y.params) : null;\n if (n.role === \"remove\") {\n if (n.id) return ok([x]);\n return fromMoved ? ok([{ id: x.id, params: { ...p, row: fromMoved.row, col: fromMoved.col } }]) : ok([]);\n }\n const moved = n.cell ? moveSheetAnchorCell(n.cell, n.sheet, y.id, y.params) : null;\n if (n.role === \"write\") {\n if (!moved) return ok([], [], tellNote(x, n));\n return ok([moved === n.cell ? x : { id: x.id, params: { ...p, row: moved.row, col: moved.col } }]);\n }\n // a move: its target moves; a target that was removed takes the note with it\n if (!moved) return n.id ? ok([removeNoteOf(n.sheet, n.id)], [], tellNote(x, n)) : ok([], [], tellNote(x, n));\n const params: Record<string, any> = { ...p, newPosition: { ...p.newPosition, row: moved.row, col: moved.col } };\n if (fromMoved) Object.assign(params, { row: fromMoved.row, col: fromMoved.col });\n return ok([{ id: x.id, params }]);\n }\n // both are notes\n const m = noteRef(y);\n if (n.sheet !== m.sheet) return ok([x]);\n if (!n.id || !m.id) return noteBarrier(\"a note change addressed by cell against another note change on its sheet\");\n if (n.id !== m.id) {\n const sameCell = n.cell && m.cell && n.cell.row === m.cell.row && n.cell.col === m.cell.col;\n return sameCell ? noteBarrier(\"two notes written to one cell at once\") : ok([x]);\n }\n if (n.role === \"remove\") return ok([x]);\n if (m.role === \"remove\") return ok([], [], tellNote(x, n));\n if (n.role === \"write\" && m.role === \"move\") {\n // a full write over a move takes the move's target cell\n return m.cell ? ok([{ id: x.id, params: { ...x.params, row: m.cell.row, col: m.cell.col } }]) : ok([x]);\n }\n if (n.role === \"move\" && m.role === \"write\") return ok([x]);\n const dx = noteDerived(x);\n const dy = noteDerived(y);\n // two structural ops' fixups: each target already went through the other op's rows and columns\n if (dx && dy) return ok([x]);\n const xWins = dx !== dy ? !dx : side === \"left\";\n return xWins ? ok([x], tellNote(y, m)) : ok([], [], tellNote(x, n));\n}\n\n/** x over y when either is an anchor mutation; null when neither is (the sheet transformer carries on). */\nexport function transformCommentAnchor(x: SheetMutation, y: SheetMutation, side: Side): TransformResult | null {\n const note = transformCellNote(x, y, side); // cell notes are the same kind of record (C5f)\n if (note) return note;\n const xa = x.id === SET_SHEET_COMMENT_ANCHOR;\n const ya = y.id === SET_SHEET_COMMENT_ANCHOR;\n if (!xa && !ya) return null;\n if (!xa) return ok([x]); // an anchor never moves content\n if (ya) {\n // anchors are keyed by sheet and id: the same id on another sheet is another anchor\n if (x.params.anchorId !== y.params.anchorId || x.params.subUnitId !== y.params.subUnitId) return ok([x]);\n return side === \"left\" ? ok([x], [label(y)]) : ok([], [], [label(x)]);\n }\n const cell = cellOf(x);\n if (!cell) return ok([x]); // taking an anchor out commutes with every move\n const moved = moveSheetAnchorCell(cell, String(x.params.subUnitId ?? \"\"), y.id, y.params);\n if (!moved) return ok([], [], [label(x)]);\n return ok([moved === cell ? x : { id: x.id, params: { ...x.params, cell: moved } }]);\n}\n"]}
@@ -12,6 +12,21 @@
12
12
  * Every seat moves its stored anchors through each structural op it applies with `moveSheetAnchorCell` — the
13
13
  * same function — so a seat that set an anchor before a peer's row insert and a seat that received the anchor
14
14
  * after it end with the same cell.
15
+ *
16
+ * Univer's cell notes (`@univerjs/sheets-note`, C5f) are the same kind of record — keyed by note id, anchored to one
17
+ * cell, never moving content — and transform here too (`transformCellNote`, called first). Their params name the
18
+ * sheet `sheetId`. Facets: a full write (`update-note`: content and cell), a move (`update-note-position`: the cell)
19
+ * and a remove (`remove-note`).
20
+ * - a note over a structural op on its sheet: its cell moves (`moveSheetAnchorCell`); a write whose cell was
21
+ * removed is dropped (the note went with its row), a move whose target was removed becomes a remove;
22
+ * - a structural op over a note: a raw structural mutation runs no plugin ref-range handler synchronously (each
23
+ * seat's watcher moves notes a microtask later), so the op carries the fixup the note is owed — its move, or
24
+ * its remove — and every seat (the fold included) holds the same cells the moment it applies the op;
25
+ * - the same note: a remove wins; a full write over a move takes the move's target cell; two structural ops'
26
+ * fixups both stand (each target went through the other op); otherwise a person's write or move beats a
27
+ * structural op's derived one, else the later commit wins;
28
+ * - two different notes written to one cell at once, or a note change addressed by cell instead of id against
29
+ * another note change on its sheet: barrier (the resource keeps one note per cell, by insertion order).
15
30
  */
16
31
 
17
32
  declare const SET_SHEET_COMMENT_ANCHOR = "matrx.mutation.set-comment-anchor";
@@ -12,6 +12,21 @@
12
12
  * Every seat moves its stored anchors through each structural op it applies with `moveSheetAnchorCell` — the
13
13
  * same function — so a seat that set an anchor before a peer's row insert and a seat that received the anchor
14
14
  * after it end with the same cell.
15
+ *
16
+ * Univer's cell notes (`@univerjs/sheets-note`, C5f) are the same kind of record — keyed by note id, anchored to one
17
+ * cell, never moving content — and transform here too (`transformCellNote`, called first). Their params name the
18
+ * sheet `sheetId`. Facets: a full write (`update-note`: content and cell), a move (`update-note-position`: the cell)
19
+ * and a remove (`remove-note`).
20
+ * - a note over a structural op on its sheet: its cell moves (`moveSheetAnchorCell`); a write whose cell was
21
+ * removed is dropped (the note went with its row), a move whose target was removed becomes a remove;
22
+ * - a structural op over a note: a raw structural mutation runs no plugin ref-range handler synchronously (each
23
+ * seat's watcher moves notes a microtask later), so the op carries the fixup the note is owed — its move, or
24
+ * its remove — and every seat (the fold included) holds the same cells the moment it applies the op;
25
+ * - the same note: a remove wins; a full write over a move takes the move's target cell; two structural ops'
26
+ * fixups both stand (each target went through the other op); otherwise a person's write or move beats a
27
+ * structural op's derived one, else the later commit wins;
28
+ * - two different notes written to one cell at once, or a note change addressed by cell instead of id against
29
+ * another note change on its sheet: barrier (the resource keeps one note per cell, by insertion order).
15
30
  */
16
31
 
17
32
  declare const SET_SHEET_COMMENT_ANCHOR = "matrx.mutation.set-comment-anchor";
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/sheets/comment-anchors.ts","../src/collab/sheets/transform/axis.ts","../src/collab/sheets/comment-anchor-transform.ts"],"sourcesContent":["/**\n * Cell comment anchors in a workbook (COLLAB-DESIGN §8, unit C9).\n *\n * The anchor is a workbook resource (`SHEET_MATRX_COMMENT_ANCHORS_PLUGIN`): per sheet, anchor id → cell. It is\n * changed only by `matrx.mutation.set-comment-anchor`, which travels in the ordered room like every covered\n * mutation (its transform is `collab/sheets/comment-anchor-transform.ts`), and it follows every row/column\n * insert, remove and move this seat applies — its own, a peer's, an undo — through `moveSheetAnchorCell`, the\n * same function the transform uses, so every seat holds the same cells. A removed row, column or sheet takes the\n * anchor out: the thread in `platform.comments` (part_anchor `anchor:<id>`) is then orphaned, never dropped.\n *\n * `installSheetCommentAnchors(univer)` must run before the workbook unit is created (the resource loads with it).\n */\nimport { CommandType, ICommandService, IResourceManagerService, UndoCommandId, UniverInstanceType } from \"@univerjs/core\";\n\nimport { SET_SHEET_COMMENT_ANCHOR, moveSheetAnchorCell, structuralAxisOp, type SheetAnchorCell } from \"../collab/sheets/comment-anchor-transform\";\n\nexport { SET_SHEET_COMMENT_ANCHOR, sheetCellName, type SheetAnchorCell } from \"../collab/sheets/comment-anchor-transform\";\n\nexport const SHEET_COMMENT_ANCHORS_RESOURCE = \"SHEET_MATRX_COMMENT_ANCHORS_PLUGIN\" as const;\n\n/** Per sheet id: anchor id → cell. */\nexport type SheetAnchors = Record<string, Record<string, SheetAnchorCell>>;\n\nexport interface SetSheetCommentAnchorParams {\n unitId: string;\n subUnitId: string;\n anchorId: string;\n /** null takes the anchor out. */\n cell: SheetAnchorCell | null;\n}\n\n/** Apply one mutation to a workbook's anchors (pure; returns the same object when nothing changed). */\nexport function applyToSheetAnchors(anchors: SheetAnchors, id: string, params: Readonly<Record<string, unknown>>): SheetAnchors {\n if (id === SET_SHEET_COMMENT_ANCHOR) {\n const p = params as unknown as SetSheetCommentAnchorParams;\n const sheet = { ...(anchors[p.subUnitId] ?? {}) };\n if (p.cell) sheet[p.anchorId] = { row: p.cell.row, col: p.cell.col };\n else if (sheet[p.anchorId]) delete sheet[p.anchorId];\n else return anchors;\n const next = { ...anchors, [p.subUnitId]: sheet };\n if (Object.keys(sheet).length === 0) delete next[p.subUnitId];\n return next;\n }\n const sheetId = String(params.subUnitId ?? \"\");\n const sheet = anchors[sheetId];\n if (!sheet) return anchors;\n let changed = false;\n const moved: Record<string, SheetAnchorCell> = {};\n for (const [anchorId, cell] of Object.entries(sheet)) {\n const to = moveSheetAnchorCell(cell, sheetId, id, params);\n if (to !== cell) changed = true;\n if (to) moved[anchorId] = to;\n }\n if (!changed) return anchors;\n const next = { ...anchors, [sheetId]: moved };\n if (Object.keys(moved).length === 0) delete next[sheetId];\n return next;\n}\n\n/** The resource's stored form: keys sorted so every seat writes the same bytes (the state hash reads them). */\nexport function sheetAnchorsToJson(anchors: SheetAnchors | undefined): string {\n if (!anchors) return \"\";\n const sheets = Object.keys(anchors).filter((s) => Object.keys(anchors[s] ?? {}).length > 0).sort();\n if (sheets.length === 0) return \"\";\n const out: Record<string, Record<string, SheetAnchorCell>> = {};\n for (const s of sheets) {\n const ids = Object.keys(anchors[s]!).sort();\n out[s] = Object.fromEntries(ids.map((id) => [id, { row: anchors[s]![id]!.row, col: anchors[s]![id]!.col }]));\n }\n return JSON.stringify(out);\n}\n\nexport function sheetAnchorsFromJson(bytes: string | null | undefined): SheetAnchors {\n if (!bytes) return {};\n try {\n const v = JSON.parse(bytes) as unknown;\n if (!v || typeof v !== \"object\") return {};\n const out: SheetAnchors = {};\n for (const [sheet, ids] of Object.entries(v as Record<string, unknown>)) {\n if (!ids || typeof ids !== \"object\") continue;\n for (const [id, c] of Object.entries(ids as Record<string, unknown>)) {\n const cell = c as SheetAnchorCell;\n if (typeof cell?.row === \"number\" && typeof cell?.col === \"number\") (out[sheet] ??= {})[id] = { row: cell.row, col: cell.col };\n }\n }\n return out;\n } catch {\n return {};\n }\n}\n\n/**\n * What a removal took out, kept on the seat that may undo it (Univer's undo stack is per seat). Undoing a row, column or\n * sheet delete re-creates the cells; the anchors that were inside come back with them (the thread is no longer orphaned).\n */\ninterface RemovedAnchors {\n sheetId: string;\n /** null: the whole sheet was removed. */\n axis: \"row\" | \"col\" | null;\n start: number;\n count: number;\n anchors: Array<{ id: string; cell: SheetAnchorCell }>;\n}\nconst REMOVED_KEEP = 25;\n\n/** Anchors in `before` that `after` no longer has, on the sheet a removal touched. */\nfunction removedBy(before: SheetAnchors, after: SheetAnchors, id: string, params: Readonly<Record<string, unknown>>): RemovedAnchors | null {\n const sheetId = String(params.subUnitId ?? \"\");\n const gone = Object.entries(before[sheetId] ?? {})\n .filter(([anchorId]) => !after[sheetId]?.[anchorId])\n .map(([anchorId, cell]) => ({ id: anchorId, cell }));\n if (!gone.length) return null;\n if (id === \"sheet.mutation.remove-sheet\") return { sheetId, axis: null, start: 0, count: 0, anchors: gone };\n const op = structuralAxisOp(id, params);\n if (!op || op.op.kind !== \"remove\") return null;\n return { sheetId, axis: op.axis, start: op.op.start, count: op.op.count, anchors: gone };\n}\n\nexport interface SheetCommentAnchorsHandle {\n /** The anchors of one workbook now. */\n get(unitId: string): SheetAnchors;\n /** Anchor a cell (a new thread) — or take an anchor out with `cell: null` — through the command service. */\n set(params: SetSheetCommentAnchorParams): boolean;\n /** Called after any change to a workbook's anchors (set, moved, removed, loaded). */\n onChange(listener: (unitId: string) => void): () => void;\n dispose(): void;\n}\n\ntype Injector = { get<T>(token: unknown): T };\nconst INSTALLED = Symbol.for(\"ai-matrx.office.sheet-comment-anchors\");\n\n/** Register the anchor mutation, the resource and the structural listener on one Univer (idempotent). */\nexport function installSheetCommentAnchors(univer: unknown): SheetCommentAnchorsHandle {\n const holder = univer as { [INSTALLED]?: SheetCommentAnchorsHandle; __getInjector?: () => Injector };\n if (holder[INSTALLED]) return holder[INSTALLED]!;\n const injector = holder.__getInjector?.();\n if (!injector) throw new Error(\"installSheetCommentAnchors: this Univer exposes no injector\");\n const commands = injector.get<ICommandService>(ICommandService);\n const resources = injector.get<{ registerPluginResource: (hook: unknown) => { dispose(): void } }>(IResourceManagerService);\n\n const byUnit = new Map<string, SheetAnchors>();\n const listeners = new Set<(unitId: string) => void>();\n const changed = (unitId: string) => {\n for (const l of Array.from(listeners)) {\n try {\n l(unitId);\n } catch {\n /* a listener never stops the editor */\n }\n }\n };\n const removed = new Map<string, RemovedAnchors[]>();\n let undoing = 0;\n const apply = (unitId: string, id: string, params: Record<string, unknown>) => {\n const before = byUnit.get(unitId) ?? {};\n const after = applyToSheetAnchors(before, id, params);\n if (after === before) return;\n byUnit.set(unitId, after);\n const gone = removedBy(before, after, id, params);\n if (gone) removed.set(unitId, [...(removed.get(unitId) ?? []), gone].slice(-REMOVED_KEEP));\n changed(unitId);\n };\n /**\n * An undo re-created cells a removal had taken anchors out of: the anchors to put back once the undo is done. They are\n * set AFTER the undo's own mutations (never inside the insert-row's callback): a seat's room log must read\n * `insert-row, then set-anchor`; the other order replays as \"set the anchor at row 4, then insert a row at 4\", which\n * pushes the anchor one row down on every seat that rebuilds from the log (live: B5 came back as B6).\n */\n const toRestore: Array<{ unitId: string; sheetId: string; anchors: Array<{ id: string; cell: SheetAnchorCell }> }> = [];\n const restoreAfterUndo = (unitId: string, id: string, params: Record<string, unknown>) => {\n const list = removed.get(unitId);\n if (!list?.length) return;\n const sheetId = String(params.subUnitId ?? \"\");\n const op = id === \"sheet.mutation.insert-sheet\" ? null : structuralAxisOp(id, params);\n const wholeSheet = id === \"sheet.mutation.insert-sheet\";\n const matches = (r: RemovedAnchors) =>\n wholeSheet\n ? r.axis === null && r.sheetId === String((params.sheet as { id?: string } | undefined)?.id ?? params.subUnitId ?? \"\")\n : !!op && op.op.kind === \"insert\" && r.axis === op.axis && r.sheetId === sheetId && r.start === op.op.at && r.count === op.op.count;\n let at = -1;\n for (let i = list.length - 1; i >= 0 && at < 0; i--) if (matches(list[i]!)) at = i;\n if (at < 0) return;\n const [hit] = list.splice(at, 1);\n toRestore.push({ unitId, sheetId: hit!.sheetId, anchors: hit!.anchors });\n };\n const flushRestores = () => {\n for (const r of toRestore.splice(0)) {\n for (const a of r.anchors) {\n if ((byUnit.get(r.unitId) ?? {})[r.sheetId]?.[a.id]) continue;\n commands.syncExecuteCommand(SET_SHEET_COMMENT_ANCHOR, { unitId: r.unitId, subUnitId: r.sheetId, anchorId: a.id, cell: a.cell });\n }\n }\n };\n\n const disposables: Array<{ dispose(): void }> = [];\n disposables.push(\n commands.registerCommand({\n id: SET_SHEET_COMMENT_ANCHOR,\n type: CommandType.MUTATION,\n handler: (_accessor: unknown, params?: SetSheetCommentAnchorParams) => {\n if (!params?.unitId || !params.subUnitId || !params.anchorId) return false;\n apply(params.unitId, SET_SHEET_COMMENT_ANCHOR, params as unknown as Record<string, unknown>);\n return true;\n },\n } as never),\n );\n disposables.push(\n resources.registerPluginResource({\n pluginName: SHEET_COMMENT_ANCHORS_RESOURCE,\n businesses: [UniverInstanceType.UNIVER_SHEET],\n toJson: (unitId: string) => sheetAnchorsToJson(byUnit.get(unitId)),\n parseJson: (bytes: string) => sheetAnchorsFromJson(bytes),\n onLoad: (unitId: string, value: SheetAnchors) => {\n byUnit.set(unitId, value ?? {});\n changed(unitId);\n },\n onUnLoad: (unitId: string) => {\n byUnit.delete(unitId);\n changed(unitId);\n },\n }),\n );\n disposables.push(\n commands.onCommandExecuted((info: { id: string; type?: CommandType; params?: unknown }) => {\n if (info.type !== CommandType.MUTATION || info.id === SET_SHEET_COMMENT_ANCHOR) return;\n const p = info.params as Record<string, unknown> | undefined;\n const unitId = typeof p?.unitId === \"string\" ? p.unitId : null;\n if (!unitId || !byUnit.has(unitId)) return;\n apply(unitId, info.id, p!);\n if (undoing > 0) restoreAfterUndo(unitId, info.id, p!);\n }),\n );\n disposables.push(\n commands.beforeCommandExecuted((info: { id: string }) => {\n if (info.id === UndoCommandId) undoing++;\n }),\n );\n disposables.push(\n commands.onCommandExecuted((info: { id: string }) => {\n if (info.id === UndoCommandId && undoing > 0) {\n undoing--;\n if (undoing === 0) flushRestores();\n }\n }),\n );\n\n const handle: SheetCommentAnchorsHandle = {\n get: (unitId) => byUnit.get(unitId) ?? {},\n set: (params) => commands.syncExecuteCommand(SET_SHEET_COMMENT_ANCHOR, params) === true,\n onChange: (l) => {\n listeners.add(l);\n return () => listeners.delete(l);\n },\n dispose: () => {\n for (const d of disposables.splice(0)) d.dispose();\n listeners.clear();\n delete holder[INSTALLED];\n },\n };\n holder[INSTALLED] = handle;\n return handle;\n}\n","/**\n * One-dimensional structural algebra for rows and columns: insert, remove and move, the index map each\n * induces, and S-over-S transforms. Index arithmetic is ours (not Univer's ref-range helpers) because ties\n * need the side rule and overlapping removes must subtract, which no free helper does.\n *\n * Semantics pinned to Univer 1.0.4:\n * - insert(at, n): new rows occupy [at, at+n); old row i >= at moves to i+n (the row AT `at` moves down).\n * - remove(start, n): rows [start, start+n) go; i >= start+n moves to i-n.\n * - move(from, n, to): `moveMatrixArray` — rows [from, from+n) are placed before old row `to`. Forward\n * (to >= from+n): block lands at [to-n, to). Backward (to < from): block lands at [to, to+n). A `to` in\n * [from, from+n] moves nothing (Univer throws for [from, from+n)); we treat it as a no-op.\n */\nimport type { Side } from \"./types\";\n\nexport type Axis = \"row\" | \"col\";\n\nexport type AxisOp =\n | { kind: \"insert\"; at: number; count: number }\n | { kind: \"remove\"; start: number; count: number }\n | { kind: \"move\"; from: number; count: number; to: number };\n\nexport function isNoopMove(op: { from: number; count: number; to: number }): boolean {\n return op.count <= 0 || (op.to >= op.from && op.to <= op.from + op.count);\n}\n\n/** Where the moved block starts after the move. */\nexport function movedBlockStart(op: { from: number; count: number; to: number }): number {\n return op.to > op.from ? op.to - op.count : op.to;\n}\n\n/** Old index → new index, or null when the index is removed. */\nexport function mapIndex(op: AxisOp, i: number): number | null {\n switch (op.kind) {\n case \"insert\":\n return i >= op.at ? i + op.count : i;\n case \"remove\":\n if (i < op.start) return i;\n if (i < op.start + op.count) return null;\n return i - op.count;\n case \"move\": {\n if (isNoopMove(op)) return i;\n const { from, count, to } = op;\n if (i >= from && i < from + count) return movedBlockStart(op) + (i - from);\n if (to > from) return i >= from + count && i < to ? i - count : i;\n return i >= to && i < from ? i + count : i;\n }\n }\n}\n\n/** Contiguous runs of a sorted, de-duplicated index list. */\nexport function runs(indices: Iterable<number>): Array<{ start: number; count: number }> {\n const sorted = [...new Set(indices)].sort((a, b) => a - b);\n const out: Array<{ start: number; count: number }> = [];\n for (const i of sorted) {\n const last = out[out.length - 1];\n if (last && last.start + last.count === i) last.count++;\n else out.push({ start: i, count: 1 });\n }\n return out;\n}\n\n/**\n * A gap (insert position: \"before old row p\") through a move. Gaps strictly inside the block ride with\n * it; any other gap is a gap of the non-block rows. At the move's own target gap an insert lands BEFORE\n * the block (side-independent; `transformAxis(move, insert)` keeps the same order).\n */\nfunction mapGapThroughMove(p: number, mv: { from: number; count: number; to: number }): number {\n const { from, count, to } = mv;\n if (p > from && p < from + count) return movedBlockStart(mv) + (p - from);\n const g = p <= from ? p : p - count; // gap index among the non-block rows\n const tg = to <= from ? to : to - count;\n return g <= tg ? g : g + count;\n}\n\n/**\n * x over y on ONE axis. Returns the list of axis ops that apply after y (empty = x vanished), or null when\n * the pair has no transform (the caller turns that into a barrier).\n */\nexport function transformAxis(x: AxisOp, y: AxisOp, side: Side): AxisOp[] | null {\n if (y.kind === \"move\" && isNoopMove(y)) return [x];\n if (x.kind === \"move\" && isNoopMove(x)) return [];\n switch (x.kind) {\n case \"insert\": {\n if (y.kind === \"insert\") {\n if (x.at < y.at || (x.at === y.at && side === \"left\")) return [x];\n return [{ ...x, at: x.at + y.count }];\n }\n if (y.kind === \"remove\") {\n if (x.at <= y.start) return [x];\n if (x.at >= y.start + y.count) return [{ ...x, at: x.at - y.count }];\n return [{ ...x, at: y.start }]; // an insert inside a removed span survives at its start\n }\n return [{ ...x, at: mapGapThroughMove(x.at, y) }];\n }\n case \"remove\": {\n const kept: number[] = [];\n for (let i = x.start; i < x.start + x.count; i++) {\n const m = mapIndex(y, i);\n if (m !== null) kept.push(m);\n }\n if (y.kind === \"insert\" || y.kind === \"remove\" || y.kind === \"move\") {\n // Removes are emitted highest first so each index is still valid when it applies.\n return runs(kept)\n .reverse()\n .map((r) => ({ kind: \"remove\" as const, start: r.start, count: r.count }));\n }\n return null;\n }\n case \"move\": {\n if (y.kind === \"insert\") {\n const inside = y.at > x.from && y.at < x.from + x.count;\n const from = inside || y.at > x.from ? x.from : x.from + y.count;\n const count = inside ? x.count + y.count : x.count;\n const to = x.to >= y.at ? x.to + y.count : x.to;\n const out = { kind: \"move\" as const, from, count, to };\n return isNoopMove(out) ? [] : [out];\n }\n if (y.kind === \"remove\") {\n const kept: number[] = [];\n for (let i = x.from; i < x.from + x.count; i++) {\n const m = mapIndex(y, i);\n if (m !== null) kept.push(m);\n }\n if (kept.length === 0) return [];\n const r = runs(kept);\n if (r.length !== 1) return null;\n const to = x.to <= y.start ? x.to : x.to >= y.start + y.count ? x.to - y.count : y.start;\n const out = { kind: \"move\" as const, from: r[0]!.start, count: r[0]!.count, to };\n return isNoopMove(out) ? [] : [out];\n }\n // move over move. The same rows moved twice: the later move wins, the earlier one is dropped.\n // Blocks that only partly overlap have no transform yet.\n const overlap = Math.min(x.from + x.count, y.from + y.count) - Math.max(x.from, y.from);\n if (overlap > 0) {\n if (x.from !== y.from || x.count !== y.count) return null;\n if (side === \"right\") return [];\n const out = { kind: \"move\" as const, from: movedBlockStart(y), count: x.count, to: mapGapThroughMove(x.to, y) };\n return isNoopMove(out) ? [] : [out];\n }\n // Disjoint blocks: x's rows through y must stay contiguous; the target gap goes through y.\n const mapped: number[] = [];\n for (let i = x.from; i < x.from + x.count; i++) mapped.push(mapIndex(y, i)!);\n const r = runs(mapped);\n if (r.length !== 1) return null;\n let to: number;\n if (x.to > y.from && x.to < y.from + y.count) to = movedBlockStart(y) + (x.to - y.from);\n else to = mapGapThroughMove(x.to, y) + (x.to === y.to && side === \"right\" ? y.count : 0); // same target: later block first\n const out = { kind: \"move\" as const, from: r[0]!.start, count: r[0]!.count, to };\n if (to > out.from && to < out.from + out.count) return null;\n return isNoopMove(out) ? [] : [out];\n }\n }\n}\n","/**\n * The sheet comment-anchor transform (COLLAB-DESIGN §8 + §10 class R, unit C9). Its own file; the sheet\n * transformer calls it first for every pair (`transformMutation`) and the registry declares its mutation id.\n *\n * A cell comment is anchored by `matrx.mutation.set-comment-anchor` `{ subUnitId, anchorId, cell: {row, col} | null }`\n * (null takes the anchor out). The anchor is a ranged resource keyed by id:\n * - anchor over a structural op on its sheet: the cell moves by the same index arithmetic as cells (`mapIndex`);\n * a cell whose row or column was removed DROPS the anchor (its author is told) and the thread is orphaned;\n * - anchor over the removal of its sheet: dropped the same way;\n * - anchor over the same anchor id: the later commit wins (§6.4), the earlier side is told;\n * - anything else over an anchor, and an anchor over anything else: unchanged (anchors never move content).\n * Every seat moves its stored anchors through each structural op it applies with `moveSheetAnchorCell` — the\n * same function — so a seat that set an anchor before a peer's row insert and a seat that received the anchor\n * after it end with the same cell.\n */\nimport { mapIndex, type AxisOp } from \"./transform/axis\";\nimport type { SheetMutation, Side, TransformResult } from \"./transform/types\";\n\nexport const SET_SHEET_COMMENT_ANCHOR = \"matrx.mutation.set-comment-anchor\";\n\n/** Mutation ids this package defines itself (never emitted by Univer; the census lists them apart). */\nexport const OWN_SHEET_MUTATION_IDS: readonly string[] = [SET_SHEET_COMMENT_ANCHOR];\n\n/**\n * A sheet op that only sets or clears comment anchors: every mutation is `matrx.mutation.set-comment-anchor`. A seat\n * that may comment but not edit sends exactly these (the door `content.collab_append` checks the same rule).\n */\nexport function isSheetsAnchorOnlyOp(op: ReadonlyArray<SheetMutation> | null | undefined): boolean {\n return !!op && op.length > 0 && op.every((m) => m.id === SET_SHEET_COMMENT_ANCHOR);\n}\n\nexport interface SheetAnchorCell {\n row: number;\n col: number;\n}\n\nconst REMOVE_SHEET = \"sheet.mutation.remove-sheet\";\n\ntype R = { startRow: number; endRow: number; startColumn: number; endColumn: number };\n\n/** The row/column op a Univer structural mutation makes, from its params (the same params on every seat). */\nexport function structuralAxisOp(id: string, params: Readonly<Record<string, unknown>>): { axis: \"row\" | \"col\"; op: AxisOp } | null {\n const p = params as Record<string, R | undefined>;\n switch (id) {\n case \"sheet.mutation.insert-row\":\n return p.range ? { axis: \"row\", op: { kind: \"insert\", at: p.range.startRow, count: p.range.endRow - p.range.startRow + 1 } } : null;\n case \"sheet.mutation.remove-rows\":\n return p.range ? { axis: \"row\", op: { kind: \"remove\", start: p.range.startRow, count: p.range.endRow - p.range.startRow + 1 } } : null;\n case \"sheet.mutation.insert-col\":\n return p.range ? { axis: \"col\", op: { kind: \"insert\", at: p.range.startColumn, count: p.range.endColumn - p.range.startColumn + 1 } } : null;\n case \"sheet.mutation.remove-col\":\n return p.range ? { axis: \"col\", op: { kind: \"remove\", start: p.range.startColumn, count: p.range.endColumn - p.range.startColumn + 1 } } : null;\n case \"sheet.mutation.move-rows\":\n return p.sourceRange && p.targetRange\n ? { axis: \"row\", op: { kind: \"move\", from: p.sourceRange.startRow, count: p.sourceRange.endRow - p.sourceRange.startRow + 1, to: p.targetRange.startRow } }\n : null;\n case \"sheet.mutation.move-columns\":\n return p.sourceRange && p.targetRange\n ? {\n axis: \"col\",\n op: { kind: \"move\", from: p.sourceRange.startColumn, count: p.sourceRange.endColumn - p.sourceRange.startColumn + 1, to: p.targetRange.startColumn },\n }\n : null;\n default:\n return null;\n }\n}\n\n/**\n * Where an anchored cell is after one applied mutation on `sheet`: the same cell, a moved cell, or null when the\n * mutation removed it (its row, its column or its sheet).\n */\nexport function moveSheetAnchorCell(cell: SheetAnchorCell, sheet: string, id: string, params: Readonly<Record<string, unknown>>): SheetAnchorCell | null {\n if (String(params.subUnitId ?? \"\") !== sheet) return cell;\n if (id === REMOVE_SHEET) return null;\n const s = structuralAxisOp(id, params);\n if (!s) return cell;\n if (s.axis === \"row\") {\n const row = mapIndex(s.op, cell.row);\n return row === null ? null : row === cell.row ? cell : { row, col: cell.col };\n }\n const col = mapIndex(s.op, cell.col);\n return col === null ? null : col === cell.col ? cell : { row: cell.row, col };\n}\n\nfunction colName(c: number): string {\n let s = \"\";\n for (let n = c + 1; n > 0; n = Math.floor((n - 1) / 26)) s = String.fromCharCode(65 + ((n - 1) % 26)) + s;\n return s;\n}\n/** \"B5\" for row 4, column 1. */\nexport const sheetCellName = (cell: SheetAnchorCell) => `${colName(cell.col)}${cell.row + 1}`;\n\nfunction cellOf(m: SheetMutation): SheetAnchorCell | null {\n const c = m.params.cell as SheetAnchorCell | null | undefined;\n return c && typeof c.row === \"number\" && typeof c.col === \"number\" ? c : null;\n}\n\nconst ok = (op: SheetMutation[], lost: string[] = [], dropped: string[] = []): TransformResult => ({ op, lost, dropped });\nconst label = (m: SheetMutation) => {\n const c = cellOf(m);\n return `${String(m.params.subUnitId ?? \"\")}!${c ? sheetCellName(c) : \"?\"} comment`;\n};\n\n/** x over y when either is an anchor mutation; null when neither is (the sheet transformer carries on). */\nexport function transformCommentAnchor(x: SheetMutation, y: SheetMutation, side: Side): TransformResult | null {\n const xa = x.id === SET_SHEET_COMMENT_ANCHOR;\n const ya = y.id === SET_SHEET_COMMENT_ANCHOR;\n if (!xa && !ya) return null;\n if (!xa) return ok([x]); // an anchor never moves content\n if (ya) {\n // anchors are keyed by sheet and id: the same id on another sheet is another anchor\n if (x.params.anchorId !== y.params.anchorId || x.params.subUnitId !== y.params.subUnitId) return ok([x]);\n return side === \"left\" ? ok([x], [label(y)]) : ok([], [], [label(x)]);\n }\n const cell = cellOf(x);\n if (!cell) return ok([x]); // taking an anchor out commutes with every move\n const moved = moveSheetAnchorCell(cell, String(x.params.subUnitId ?? \"\"), y.id, y.params);\n if (!moved) return ok([], [], [label(x)]);\n return ok([moved === cell ? x : { id: x.id, params: { ...x.params, cell: moved } }]);\n}\n"],"mappings":"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","names":["sheet","next"]}
1
+ {"version":3,"sources":["../src/sheets/comment-anchors.ts","../src/collab/sheets/transform/axis.ts","../src/collab/sheets/comment-anchor-transform.ts"],"sourcesContent":["/**\n * Cell comment anchors in a workbook (COLLAB-DESIGN §8, unit C9).\n *\n * The anchor is a workbook resource (`SHEET_MATRX_COMMENT_ANCHORS_PLUGIN`): per sheet, anchor id → cell. It is\n * changed only by `matrx.mutation.set-comment-anchor`, which travels in the ordered room like every covered\n * mutation (its transform is `collab/sheets/comment-anchor-transform.ts`), and it follows every row/column\n * insert, remove and move this seat applies — its own, a peer's, an undo — through `moveSheetAnchorCell`, the\n * same function the transform uses, so every seat holds the same cells. A removed row, column or sheet takes the\n * anchor out: the thread in `platform.comments` (part_anchor `anchor:<id>`) is then orphaned, never dropped.\n *\n * `installSheetCommentAnchors(univer)` must run before the workbook unit is created (the resource loads with it).\n */\nimport { CommandType, ICommandService, IResourceManagerService, UndoCommandId, UniverInstanceType } from \"@univerjs/core\";\n\nimport { SET_SHEET_COMMENT_ANCHOR, moveSheetAnchorCell, structuralAxisOp, type SheetAnchorCell } from \"../collab/sheets/comment-anchor-transform\";\n\nexport { SET_SHEET_COMMENT_ANCHOR, sheetCellName, type SheetAnchorCell } from \"../collab/sheets/comment-anchor-transform\";\n\nexport const SHEET_COMMENT_ANCHORS_RESOURCE = \"SHEET_MATRX_COMMENT_ANCHORS_PLUGIN\" as const;\n\n/** Per sheet id: anchor id → cell. */\nexport type SheetAnchors = Record<string, Record<string, SheetAnchorCell>>;\n\nexport interface SetSheetCommentAnchorParams {\n unitId: string;\n subUnitId: string;\n anchorId: string;\n /** null takes the anchor out. */\n cell: SheetAnchorCell | null;\n}\n\n/** Apply one mutation to a workbook's anchors (pure; returns the same object when nothing changed). */\nexport function applyToSheetAnchors(anchors: SheetAnchors, id: string, params: Readonly<Record<string, unknown>>): SheetAnchors {\n if (id === SET_SHEET_COMMENT_ANCHOR) {\n const p = params as unknown as SetSheetCommentAnchorParams;\n const sheet = { ...(anchors[p.subUnitId] ?? {}) };\n if (p.cell) sheet[p.anchorId] = { row: p.cell.row, col: p.cell.col };\n else if (sheet[p.anchorId]) delete sheet[p.anchorId];\n else return anchors;\n const next = { ...anchors, [p.subUnitId]: sheet };\n if (Object.keys(sheet).length === 0) delete next[p.subUnitId];\n return next;\n }\n const sheetId = String(params.subUnitId ?? \"\");\n const sheet = anchors[sheetId];\n if (!sheet) return anchors;\n let changed = false;\n const moved: Record<string, SheetAnchorCell> = {};\n for (const [anchorId, cell] of Object.entries(sheet)) {\n const to = moveSheetAnchorCell(cell, sheetId, id, params);\n if (to !== cell) changed = true;\n if (to) moved[anchorId] = to;\n }\n if (!changed) return anchors;\n const next = { ...anchors, [sheetId]: moved };\n if (Object.keys(moved).length === 0) delete next[sheetId];\n return next;\n}\n\n/** The resource's stored form: keys sorted so every seat writes the same bytes (the state hash reads them). */\nexport function sheetAnchorsToJson(anchors: SheetAnchors | undefined): string {\n if (!anchors) return \"\";\n const sheets = Object.keys(anchors).filter((s) => Object.keys(anchors[s] ?? {}).length > 0).sort();\n if (sheets.length === 0) return \"\";\n const out: Record<string, Record<string, SheetAnchorCell>> = {};\n for (const s of sheets) {\n const ids = Object.keys(anchors[s]!).sort();\n out[s] = Object.fromEntries(ids.map((id) => [id, { row: anchors[s]![id]!.row, col: anchors[s]![id]!.col }]));\n }\n return JSON.stringify(out);\n}\n\nexport function sheetAnchorsFromJson(bytes: string | null | undefined): SheetAnchors {\n if (!bytes) return {};\n try {\n const v = JSON.parse(bytes) as unknown;\n if (!v || typeof v !== \"object\") return {};\n const out: SheetAnchors = {};\n for (const [sheet, ids] of Object.entries(v as Record<string, unknown>)) {\n if (!ids || typeof ids !== \"object\") continue;\n for (const [id, c] of Object.entries(ids as Record<string, unknown>)) {\n const cell = c as SheetAnchorCell;\n if (typeof cell?.row === \"number\" && typeof cell?.col === \"number\") (out[sheet] ??= {})[id] = { row: cell.row, col: cell.col };\n }\n }\n return out;\n } catch {\n return {};\n }\n}\n\n/**\n * What a removal took out, kept on the seat that may undo it (Univer's undo stack is per seat). Undoing a row, column or\n * sheet delete re-creates the cells; the anchors that were inside come back with them (the thread is no longer orphaned).\n */\ninterface RemovedAnchors {\n sheetId: string;\n /** null: the whole sheet was removed. */\n axis: \"row\" | \"col\" | null;\n start: number;\n count: number;\n anchors: Array<{ id: string; cell: SheetAnchorCell }>;\n}\nconst REMOVED_KEEP = 25;\n\n/** Anchors in `before` that `after` no longer has, on the sheet a removal touched. */\nfunction removedBy(before: SheetAnchors, after: SheetAnchors, id: string, params: Readonly<Record<string, unknown>>): RemovedAnchors | null {\n const sheetId = String(params.subUnitId ?? \"\");\n const gone = Object.entries(before[sheetId] ?? {})\n .filter(([anchorId]) => !after[sheetId]?.[anchorId])\n .map(([anchorId, cell]) => ({ id: anchorId, cell }));\n if (!gone.length) return null;\n if (id === \"sheet.mutation.remove-sheet\") return { sheetId, axis: null, start: 0, count: 0, anchors: gone };\n const op = structuralAxisOp(id, params);\n if (!op || op.op.kind !== \"remove\") return null;\n return { sheetId, axis: op.axis, start: op.op.start, count: op.op.count, anchors: gone };\n}\n\nexport interface SheetCommentAnchorsHandle {\n /** The anchors of one workbook now. */\n get(unitId: string): SheetAnchors;\n /** Anchor a cell (a new thread) — or take an anchor out with `cell: null` — through the command service. */\n set(params: SetSheetCommentAnchorParams): boolean;\n /** Called after any change to a workbook's anchors (set, moved, removed, loaded). */\n onChange(listener: (unitId: string) => void): () => void;\n dispose(): void;\n}\n\ntype Injector = { get<T>(token: unknown): T };\nconst INSTALLED = Symbol.for(\"ai-matrx.office.sheet-comment-anchors\");\n\n/** Register the anchor mutation, the resource and the structural listener on one Univer (idempotent). */\nexport function installSheetCommentAnchors(univer: unknown): SheetCommentAnchorsHandle {\n const holder = univer as { [INSTALLED]?: SheetCommentAnchorsHandle; __getInjector?: () => Injector };\n if (holder[INSTALLED]) return holder[INSTALLED]!;\n const injector = holder.__getInjector?.();\n if (!injector) throw new Error(\"installSheetCommentAnchors: this Univer exposes no injector\");\n const commands = injector.get<ICommandService>(ICommandService);\n const resources = injector.get<{ registerPluginResource: (hook: unknown) => { dispose(): void } }>(IResourceManagerService);\n\n const byUnit = new Map<string, SheetAnchors>();\n const listeners = new Set<(unitId: string) => void>();\n const changed = (unitId: string) => {\n for (const l of Array.from(listeners)) {\n try {\n l(unitId);\n } catch {\n /* a listener never stops the editor */\n }\n }\n };\n const removed = new Map<string, RemovedAnchors[]>();\n let undoing = 0;\n const apply = (unitId: string, id: string, params: Record<string, unknown>) => {\n const before = byUnit.get(unitId) ?? {};\n const after = applyToSheetAnchors(before, id, params);\n if (after === before) return;\n byUnit.set(unitId, after);\n const gone = removedBy(before, after, id, params);\n if (gone) removed.set(unitId, [...(removed.get(unitId) ?? []), gone].slice(-REMOVED_KEEP));\n changed(unitId);\n };\n /**\n * An undo re-created cells a removal had taken anchors out of: the anchors to put back once the undo is done. They are\n * set AFTER the undo's own mutations (never inside the insert-row's callback): a seat's room log must read\n * `insert-row, then set-anchor`; the other order replays as \"set the anchor at row 4, then insert a row at 4\", which\n * pushes the anchor one row down on every seat that rebuilds from the log (live: B5 came back as B6).\n */\n const toRestore: Array<{ unitId: string; sheetId: string; anchors: Array<{ id: string; cell: SheetAnchorCell }> }> = [];\n const restoreAfterUndo = (unitId: string, id: string, params: Record<string, unknown>) => {\n const list = removed.get(unitId);\n if (!list?.length) return;\n const sheetId = String(params.subUnitId ?? \"\");\n const op = id === \"sheet.mutation.insert-sheet\" ? null : structuralAxisOp(id, params);\n const wholeSheet = id === \"sheet.mutation.insert-sheet\";\n const matches = (r: RemovedAnchors) =>\n wholeSheet\n ? r.axis === null && r.sheetId === String((params.sheet as { id?: string } | undefined)?.id ?? params.subUnitId ?? \"\")\n : !!op && op.op.kind === \"insert\" && r.axis === op.axis && r.sheetId === sheetId && r.start === op.op.at && r.count === op.op.count;\n let at = -1;\n for (let i = list.length - 1; i >= 0 && at < 0; i--) if (matches(list[i]!)) at = i;\n if (at < 0) return;\n const [hit] = list.splice(at, 1);\n toRestore.push({ unitId, sheetId: hit!.sheetId, anchors: hit!.anchors });\n };\n const flushRestores = () => {\n for (const r of toRestore.splice(0)) {\n for (const a of r.anchors) {\n if ((byUnit.get(r.unitId) ?? {})[r.sheetId]?.[a.id]) continue;\n commands.syncExecuteCommand(SET_SHEET_COMMENT_ANCHOR, { unitId: r.unitId, subUnitId: r.sheetId, anchorId: a.id, cell: a.cell });\n }\n }\n };\n\n const disposables: Array<{ dispose(): void }> = [];\n disposables.push(\n commands.registerCommand({\n id: SET_SHEET_COMMENT_ANCHOR,\n type: CommandType.MUTATION,\n handler: (_accessor: unknown, params?: SetSheetCommentAnchorParams) => {\n if (!params?.unitId || !params.subUnitId || !params.anchorId) return false;\n apply(params.unitId, SET_SHEET_COMMENT_ANCHOR, params as unknown as Record<string, unknown>);\n return true;\n },\n } as never),\n );\n disposables.push(\n resources.registerPluginResource({\n pluginName: SHEET_COMMENT_ANCHORS_RESOURCE,\n businesses: [UniverInstanceType.UNIVER_SHEET],\n toJson: (unitId: string) => sheetAnchorsToJson(byUnit.get(unitId)),\n parseJson: (bytes: string) => sheetAnchorsFromJson(bytes),\n onLoad: (unitId: string, value: SheetAnchors) => {\n byUnit.set(unitId, value ?? {});\n changed(unitId);\n },\n onUnLoad: (unitId: string) => {\n byUnit.delete(unitId);\n changed(unitId);\n },\n }),\n );\n disposables.push(\n commands.onCommandExecuted((info: { id: string; type?: CommandType; params?: unknown }) => {\n if (info.type !== CommandType.MUTATION || info.id === SET_SHEET_COMMENT_ANCHOR) return;\n const p = info.params as Record<string, unknown> | undefined;\n const unitId = typeof p?.unitId === \"string\" ? p.unitId : null;\n if (!unitId || !byUnit.has(unitId)) return;\n apply(unitId, info.id, p!);\n if (undoing > 0) restoreAfterUndo(unitId, info.id, p!);\n }),\n );\n disposables.push(\n commands.beforeCommandExecuted((info: { id: string }) => {\n if (info.id === UndoCommandId) undoing++;\n }),\n );\n disposables.push(\n commands.onCommandExecuted((info: { id: string }) => {\n if (info.id === UndoCommandId && undoing > 0) {\n undoing--;\n if (undoing === 0) flushRestores();\n }\n }),\n );\n\n const handle: SheetCommentAnchorsHandle = {\n get: (unitId) => byUnit.get(unitId) ?? {},\n set: (params) => commands.syncExecuteCommand(SET_SHEET_COMMENT_ANCHOR, params) === true,\n onChange: (l) => {\n listeners.add(l);\n return () => listeners.delete(l);\n },\n dispose: () => {\n for (const d of disposables.splice(0)) d.dispose();\n listeners.clear();\n delete holder[INSTALLED];\n },\n };\n holder[INSTALLED] = handle;\n return handle;\n}\n","/**\n * One-dimensional structural algebra for rows and columns: insert, remove and move, the index map each\n * induces, and S-over-S transforms. Index arithmetic is ours (not Univer's ref-range helpers) because ties\n * need the side rule and overlapping removes must subtract, which no free helper does.\n *\n * Semantics pinned to Univer 1.0.4:\n * - insert(at, n): new rows occupy [at, at+n); old row i >= at moves to i+n (the row AT `at` moves down).\n * - remove(start, n): rows [start, start+n) go; i >= start+n moves to i-n.\n * - move(from, n, to): `moveMatrixArray` — rows [from, from+n) are placed before old row `to`. Forward\n * (to >= from+n): block lands at [to-n, to). Backward (to < from): block lands at [to, to+n). A `to` in\n * [from, from+n] moves nothing (Univer throws for [from, from+n)); we treat it as a no-op.\n */\nimport type { Side } from \"./types\";\n\nexport type Axis = \"row\" | \"col\";\n\nexport type AxisOp =\n | { kind: \"insert\"; at: number; count: number }\n | { kind: \"remove\"; start: number; count: number }\n | { kind: \"move\"; from: number; count: number; to: number };\n\nexport function isNoopMove(op: { from: number; count: number; to: number }): boolean {\n return op.count <= 0 || (op.to >= op.from && op.to <= op.from + op.count);\n}\n\n/** Where the moved block starts after the move. */\nexport function movedBlockStart(op: { from: number; count: number; to: number }): number {\n return op.to > op.from ? op.to - op.count : op.to;\n}\n\n/** Old index → new index, or null when the index is removed. */\nexport function mapIndex(op: AxisOp, i: number): number | null {\n switch (op.kind) {\n case \"insert\":\n return i >= op.at ? i + op.count : i;\n case \"remove\":\n if (i < op.start) return i;\n if (i < op.start + op.count) return null;\n return i - op.count;\n case \"move\": {\n if (isNoopMove(op)) return i;\n const { from, count, to } = op;\n if (i >= from && i < from + count) return movedBlockStart(op) + (i - from);\n if (to > from) return i >= from + count && i < to ? i - count : i;\n return i >= to && i < from ? i + count : i;\n }\n }\n}\n\n/** Contiguous runs of a sorted, de-duplicated index list. */\nexport function runs(indices: Iterable<number>): Array<{ start: number; count: number }> {\n const sorted = [...new Set(indices)].sort((a, b) => a - b);\n const out: Array<{ start: number; count: number }> = [];\n for (const i of sorted) {\n const last = out[out.length - 1];\n if (last && last.start + last.count === i) last.count++;\n else out.push({ start: i, count: 1 });\n }\n return out;\n}\n\n/**\n * A gap (insert position: \"before old row p\") through a move. Gaps strictly inside the block ride with\n * it; any other gap is a gap of the non-block rows. At the move's own target gap an insert lands BEFORE\n * the block (side-independent; `transformAxis(move, insert)` keeps the same order).\n */\nfunction mapGapThroughMove(p: number, mv: { from: number; count: number; to: number }): number {\n const { from, count, to } = mv;\n if (p > from && p < from + count) return movedBlockStart(mv) + (p - from);\n const g = p <= from ? p : p - count; // gap index among the non-block rows\n const tg = to <= from ? to : to - count;\n return g <= tg ? g : g + count;\n}\n\n/**\n * x over y on ONE axis. Returns the list of axis ops that apply after y (empty = x vanished), or null when\n * the pair has no transform (the caller turns that into a barrier).\n */\nexport function transformAxis(x: AxisOp, y: AxisOp, side: Side): AxisOp[] | null {\n if (y.kind === \"move\" && isNoopMove(y)) return [x];\n if (x.kind === \"move\" && isNoopMove(x)) return [];\n switch (x.kind) {\n case \"insert\": {\n if (y.kind === \"insert\") {\n if (x.at < y.at || (x.at === y.at && side === \"left\")) return [x];\n return [{ ...x, at: x.at + y.count }];\n }\n if (y.kind === \"remove\") {\n if (x.at <= y.start) return [x];\n if (x.at >= y.start + y.count) return [{ ...x, at: x.at - y.count }];\n return [{ ...x, at: y.start }]; // an insert inside a removed span survives at its start\n }\n return [{ ...x, at: mapGapThroughMove(x.at, y) }];\n }\n case \"remove\": {\n const kept: number[] = [];\n for (let i = x.start; i < x.start + x.count; i++) {\n const m = mapIndex(y, i);\n if (m !== null) kept.push(m);\n }\n if (y.kind === \"insert\" || y.kind === \"remove\" || y.kind === \"move\") {\n // Removes are emitted highest first so each index is still valid when it applies.\n return runs(kept)\n .reverse()\n .map((r) => ({ kind: \"remove\" as const, start: r.start, count: r.count }));\n }\n return null;\n }\n case \"move\": {\n if (y.kind === \"insert\") {\n const inside = y.at > x.from && y.at < x.from + x.count;\n const from = inside || y.at > x.from ? x.from : x.from + y.count;\n const count = inside ? x.count + y.count : x.count;\n const to = x.to >= y.at ? x.to + y.count : x.to;\n const out = { kind: \"move\" as const, from, count, to };\n return isNoopMove(out) ? [] : [out];\n }\n if (y.kind === \"remove\") {\n const kept: number[] = [];\n for (let i = x.from; i < x.from + x.count; i++) {\n const m = mapIndex(y, i);\n if (m !== null) kept.push(m);\n }\n if (kept.length === 0) return [];\n const r = runs(kept);\n if (r.length !== 1) return null;\n const to = x.to <= y.start ? x.to : x.to >= y.start + y.count ? x.to - y.count : y.start;\n const out = { kind: \"move\" as const, from: r[0]!.start, count: r[0]!.count, to };\n return isNoopMove(out) ? [] : [out];\n }\n // move over move. The same rows moved twice: the later move wins, the earlier one is dropped.\n // Blocks that only partly overlap have no transform yet.\n const overlap = Math.min(x.from + x.count, y.from + y.count) - Math.max(x.from, y.from);\n if (overlap > 0) {\n if (x.from !== y.from || x.count !== y.count) return null;\n if (side === \"right\") return [];\n const out = { kind: \"move\" as const, from: movedBlockStart(y), count: x.count, to: mapGapThroughMove(x.to, y) };\n return isNoopMove(out) ? [] : [out];\n }\n // Disjoint blocks: x's rows through y must stay contiguous; the target gap goes through y.\n const mapped: number[] = [];\n for (let i = x.from; i < x.from + x.count; i++) mapped.push(mapIndex(y, i)!);\n const r = runs(mapped);\n if (r.length !== 1) return null;\n let to: number;\n if (x.to > y.from && x.to < y.from + y.count) to = movedBlockStart(y) + (x.to - y.from);\n else to = mapGapThroughMove(x.to, y) + (x.to === y.to && side === \"right\" ? y.count : 0); // same target: later block first\n const out = { kind: \"move\" as const, from: r[0]!.start, count: r[0]!.count, to };\n if (to > out.from && to < out.from + out.count) return null;\n return isNoopMove(out) ? [] : [out];\n }\n }\n}\n","/**\n * The sheet comment-anchor transform (COLLAB-DESIGN §8 + §10 class R, unit C9). Its own file; the sheet\n * transformer calls it first for every pair (`transformMutation`) and the registry declares its mutation id.\n *\n * A cell comment is anchored by `matrx.mutation.set-comment-anchor` `{ subUnitId, anchorId, cell: {row, col} | null }`\n * (null takes the anchor out). The anchor is a ranged resource keyed by id:\n * - anchor over a structural op on its sheet: the cell moves by the same index arithmetic as cells (`mapIndex`);\n * a cell whose row or column was removed DROPS the anchor (its author is told) and the thread is orphaned;\n * - anchor over the removal of its sheet: dropped the same way;\n * - anchor over the same anchor id: the later commit wins (§6.4), the earlier side is told;\n * - anything else over an anchor, and an anchor over anything else: unchanged (anchors never move content).\n * Every seat moves its stored anchors through each structural op it applies with `moveSheetAnchorCell` — the\n * same function — so a seat that set an anchor before a peer's row insert and a seat that received the anchor\n * after it end with the same cell.\n *\n * Univer's cell notes (`@univerjs/sheets-note`, C5f) are the same kind of record — keyed by note id, anchored to one\n * cell, never moving content — and transform here too (`transformCellNote`, called first). Their params name the\n * sheet `sheetId`. Facets: a full write (`update-note`: content and cell), a move (`update-note-position`: the cell)\n * and a remove (`remove-note`).\n * - a note over a structural op on its sheet: its cell moves (`moveSheetAnchorCell`); a write whose cell was\n * removed is dropped (the note went with its row), a move whose target was removed becomes a remove;\n * - a structural op over a note: a raw structural mutation runs no plugin ref-range handler synchronously (each\n * seat's watcher moves notes a microtask later), so the op carries the fixup the note is owed — its move, or\n * its remove — and every seat (the fold included) holds the same cells the moment it applies the op;\n * - the same note: a remove wins; a full write over a move takes the move's target cell; two structural ops'\n * fixups both stand (each target went through the other op); otherwise a person's write or move beats a\n * structural op's derived one, else the later commit wins;\n * - two different notes written to one cell at once, or a note change addressed by cell instead of id against\n * another note change on its sheet: barrier (the resource keeps one note per cell, by insertion order).\n */\nimport { mapIndex, type AxisOp } from \"./transform/axis\";\nimport type { SheetMutation, Side, TransformResult } from \"./transform/types\";\n\nexport const SET_SHEET_COMMENT_ANCHOR = \"matrx.mutation.set-comment-anchor\";\n\n/** Mutation ids this package defines itself (never emitted by Univer; the census lists them apart). */\nexport const OWN_SHEET_MUTATION_IDS: readonly string[] = [SET_SHEET_COMMENT_ANCHOR];\n\n/**\n * A sheet op that only sets or clears comment anchors: every mutation is `matrx.mutation.set-comment-anchor`. A seat\n * that may comment but not edit sends exactly these (the door `content.collab_append` checks the same rule).\n */\nexport function isSheetsAnchorOnlyOp(op: ReadonlyArray<SheetMutation> | null | undefined): boolean {\n return !!op && op.length > 0 && op.every((m) => m.id === SET_SHEET_COMMENT_ANCHOR);\n}\n\nexport interface SheetAnchorCell {\n row: number;\n col: number;\n}\n\nconst REMOVE_SHEET = \"sheet.mutation.remove-sheet\";\n\ntype R = { startRow: number; endRow: number; startColumn: number; endColumn: number };\n\n/** The row/column op a Univer structural mutation makes, from its params (the same params on every seat). */\nexport function structuralAxisOp(id: string, params: Readonly<Record<string, unknown>>): { axis: \"row\" | \"col\"; op: AxisOp } | null {\n const p = params as Record<string, R | undefined>;\n switch (id) {\n case \"sheet.mutation.insert-row\":\n return p.range ? { axis: \"row\", op: { kind: \"insert\", at: p.range.startRow, count: p.range.endRow - p.range.startRow + 1 } } : null;\n case \"sheet.mutation.remove-rows\":\n return p.range ? { axis: \"row\", op: { kind: \"remove\", start: p.range.startRow, count: p.range.endRow - p.range.startRow + 1 } } : null;\n case \"sheet.mutation.insert-col\":\n return p.range ? { axis: \"col\", op: { kind: \"insert\", at: p.range.startColumn, count: p.range.endColumn - p.range.startColumn + 1 } } : null;\n case \"sheet.mutation.remove-col\":\n return p.range ? { axis: \"col\", op: { kind: \"remove\", start: p.range.startColumn, count: p.range.endColumn - p.range.startColumn + 1 } } : null;\n case \"sheet.mutation.move-rows\":\n return p.sourceRange && p.targetRange\n ? { axis: \"row\", op: { kind: \"move\", from: p.sourceRange.startRow, count: p.sourceRange.endRow - p.sourceRange.startRow + 1, to: p.targetRange.startRow } }\n : null;\n case \"sheet.mutation.move-columns\":\n return p.sourceRange && p.targetRange\n ? {\n axis: \"col\",\n op: { kind: \"move\", from: p.sourceRange.startColumn, count: p.sourceRange.endColumn - p.sourceRange.startColumn + 1, to: p.targetRange.startColumn },\n }\n : null;\n default:\n return null;\n }\n}\n\n/**\n * Where an anchored cell is after one applied mutation on `sheet`: the same cell, a moved cell, or null when the\n * mutation removed it (its row, its column or its sheet).\n */\nexport function moveSheetAnchorCell(cell: SheetAnchorCell, sheet: string, id: string, params: Readonly<Record<string, unknown>>): SheetAnchorCell | null {\n if (String(params.subUnitId ?? \"\") !== sheet) return cell;\n if (id === REMOVE_SHEET) return null;\n const s = structuralAxisOp(id, params);\n if (!s) return cell;\n if (s.axis === \"row\") {\n const row = mapIndex(s.op, cell.row);\n return row === null ? null : row === cell.row ? cell : { row, col: cell.col };\n }\n const col = mapIndex(s.op, cell.col);\n return col === null ? null : col === cell.col ? cell : { row: cell.row, col };\n}\n\nfunction colName(c: number): string {\n let s = \"\";\n for (let n = c + 1; n > 0; n = Math.floor((n - 1) / 26)) s = String.fromCharCode(65 + ((n - 1) % 26)) + s;\n return s;\n}\n/** \"B5\" for row 4, column 1. */\nexport const sheetCellName = (cell: SheetAnchorCell) => `${colName(cell.col)}${cell.row + 1}`;\n\nfunction cellOf(m: SheetMutation): SheetAnchorCell | null {\n const c = m.params.cell as SheetAnchorCell | null | undefined;\n return c && typeof c.row === \"number\" && typeof c.col === \"number\" ? c : null;\n}\n\nconst ok = (op: SheetMutation[], lost: string[] = [], dropped: string[] = []): TransformResult => ({ op, lost, dropped });\nconst label = (m: SheetMutation) => {\n const c = cellOf(m);\n return `${String(m.params.subUnitId ?? \"\")}!${c ? sheetCellName(c) : \"?\"} comment`;\n};\n\n// ── Univer cell notes (C5f) ──────────────────────────────────────────────────────────────────────────────────\nexport const UPDATE_NOTE = \"sheet.mutation.update-note\";\nexport const REMOVE_NOTE = \"sheet.mutation.remove-note\";\nexport const UPDATE_NOTE_POSITION = \"sheet.mutation.update-note-position\";\nconst NOTE_IDS = new Set([UPDATE_NOTE, REMOVE_NOTE, UPDATE_NOTE_POSITION]);\nexport const isNoteMutation = (id: string): boolean => NOTE_IDS.has(id);\n\ntype NoteRole = \"write\" | \"move\" | \"remove\";\ninterface NoteRef {\n role: NoteRole;\n sheet: string;\n /** The note id, or null when the op addresses the note by its cell (an undo of a new note removes by cell). */\n id: string | null;\n /** Where the op leaves the note: a write's cell, a move's target; null for a remove. */\n cell: SheetAnchorCell | null;\n}\n\nconst cellAt = (row: unknown, col: unknown): SheetAnchorCell | null => (typeof row === \"number\" && typeof col === \"number\" ? { row, col } : null);\nfunction noteRef(m: SheetMutation): NoteRef {\n const p = m.params as Record<string, any>;\n const sheet = String(p.sheetId ?? \"\");\n if (m.id === UPDATE_NOTE) return { role: \"write\", sheet, id: p.note?.id ? String(p.note.id) : null, cell: cellAt(p.row, p.col) };\n if (m.id === UPDATE_NOTE_POSITION) return { role: \"move\", sheet, id: p.noteId ? String(p.noteId) : null, cell: cellAt(p.newPosition?.row, p.newPosition?.col) };\n return { role: \"remove\", sheet, id: p.noteId ? String(p.noteId) : null, cell: p.noteId ? null : cellAt(p.row, p.col) };\n}\nconst noteDerived = (m: SheetMutation) => m.params.derived === true;\nconst noteLabel = (n: NoteRef) => `${n.sheet}!${n.cell ? sheetCellName(n.cell) : \"?\"} note`;\nconst tellNote = (m: SheetMutation, n: NoteRef) => (noteDerived(m) ? [] : [noteLabel(n)]);\nconst noteBarrier = (reason: string): TransformResult => ({ barrier: true, reason: `note: ${reason}` });\nconst removeNoteOf = (sheet: string, id: string): SheetMutation => ({ id: REMOVE_NOTE, params: { sheetId: sheet, noteId: id, derived: true } });\nconst moveNoteTo = (sheet: string, id: string, cell: SheetAnchorCell): SheetMutation => ({\n id: UPDATE_NOTE_POSITION,\n params: { sheetId: sheet, noteId: id, newPosition: { row: cell.row, col: cell.col }, derived: true },\n});\n/** A mutation's sheet as `moveSheetAnchorCell` reads it (structural ops name `subUnitId`). */\nconst onSheet = (m: SheetMutation, sheet: string) => String(m.params.subUnitId ?? \"\") === sheet;\n\n/** x over y when either is a cell-note mutation; null when neither is. */\nexport function transformCellNote(x: SheetMutation, y: SheetMutation, side: Side): TransformResult | null {\n const xn = isNoteMutation(x.id);\n const yn = isNoteMutation(y.id);\n if (!xn && !yn) return null;\n if (!xn) {\n // x moves content (or not); y is a note. A structural x carries the fixup y's note is owed.\n const n = noteRef(y);\n if (n.role === \"remove\" || !n.cell || !onSheet(x, n.sheet)) return ok([x]);\n if (x.id === REMOVE_SHEET) return n.id ? ok([removeNoteOf(n.sheet, n.id), x]) : ok([x]);\n if (!structuralAxisOp(x.id, x.params)) return ok([x]);\n if (!n.id) return noteBarrier(\"a note without an id against a row or column change\");\n const moved = moveSheetAnchorCell(n.cell, n.sheet, x.id, x.params);\n if (!moved) return ok([x, removeNoteOf(n.sheet, n.id)]);\n return moved === n.cell ? ok([x]) : ok([x, moveNoteTo(n.sheet, n.id, moved)]);\n }\n const n = noteRef(x);\n if (!yn) {\n if (!onSheet(y, n.sheet)) return ok([x]);\n if (y.id === REMOVE_SHEET) return ok([], [], tellNote(x, n));\n if (!structuralAxisOp(y.id, y.params)) return ok([x]); // a note never conflicts with content\n if (n.role === \"write\" && !n.id) return noteBarrier(\"a note without an id against a row or column change\");\n const p = x.params as Record<string, any>;\n const from = cellAt(p.row, p.col);\n const fromMoved = from ? moveSheetAnchorCell(from, n.sheet, y.id, y.params) : null;\n if (n.role === \"remove\") {\n if (n.id) return ok([x]);\n return fromMoved ? ok([{ id: x.id, params: { ...p, row: fromMoved.row, col: fromMoved.col } }]) : ok([]);\n }\n const moved = n.cell ? moveSheetAnchorCell(n.cell, n.sheet, y.id, y.params) : null;\n if (n.role === \"write\") {\n if (!moved) return ok([], [], tellNote(x, n));\n return ok([moved === n.cell ? x : { id: x.id, params: { ...p, row: moved.row, col: moved.col } }]);\n }\n // a move: its target moves; a target that was removed takes the note with it\n if (!moved) return n.id ? ok([removeNoteOf(n.sheet, n.id)], [], tellNote(x, n)) : ok([], [], tellNote(x, n));\n const params: Record<string, any> = { ...p, newPosition: { ...p.newPosition, row: moved.row, col: moved.col } };\n if (fromMoved) Object.assign(params, { row: fromMoved.row, col: fromMoved.col });\n return ok([{ id: x.id, params }]);\n }\n // both are notes\n const m = noteRef(y);\n if (n.sheet !== m.sheet) return ok([x]);\n if (!n.id || !m.id) return noteBarrier(\"a note change addressed by cell against another note change on its sheet\");\n if (n.id !== m.id) {\n const sameCell = n.cell && m.cell && n.cell.row === m.cell.row && n.cell.col === m.cell.col;\n return sameCell ? noteBarrier(\"two notes written to one cell at once\") : ok([x]);\n }\n if (n.role === \"remove\") return ok([x]);\n if (m.role === \"remove\") return ok([], [], tellNote(x, n));\n if (n.role === \"write\" && m.role === \"move\") {\n // a full write over a move takes the move's target cell\n return m.cell ? ok([{ id: x.id, params: { ...x.params, row: m.cell.row, col: m.cell.col } }]) : ok([x]);\n }\n if (n.role === \"move\" && m.role === \"write\") return ok([x]);\n const dx = noteDerived(x);\n const dy = noteDerived(y);\n // two structural ops' fixups: each target already went through the other op's rows and columns\n if (dx && dy) return ok([x]);\n const xWins = dx !== dy ? !dx : side === \"left\";\n return xWins ? ok([x], tellNote(y, m)) : ok([], [], tellNote(x, n));\n}\n\n/** x over y when either is an anchor mutation; null when neither is (the sheet transformer carries on). */\nexport function transformCommentAnchor(x: SheetMutation, y: SheetMutation, side: Side): TransformResult | null {\n const note = transformCellNote(x, y, side); // cell notes are the same kind of record (C5f)\n if (note) return note;\n const xa = x.id === SET_SHEET_COMMENT_ANCHOR;\n const ya = y.id === SET_SHEET_COMMENT_ANCHOR;\n if (!xa && !ya) return null;\n if (!xa) return ok([x]); // an anchor never moves content\n if (ya) {\n // anchors are keyed by sheet and id: the same id on another sheet is another anchor\n if (x.params.anchorId !== y.params.anchorId || x.params.subUnitId !== y.params.subUnitId) return ok([x]);\n return side === \"left\" ? ok([x], [label(y)]) : ok([], [], [label(x)]);\n }\n const cell = cellOf(x);\n if (!cell) return ok([x]); // taking an anchor out commutes with every move\n const moved = moveSheetAnchorCell(cell, String(x.params.subUnitId ?? \"\"), y.id, y.params);\n if (!moved) return ok([], [], [label(x)]);\n return ok([moved === cell ? x : { id: x.id, params: { ...x.params, cell: moved } }]);\n}\n"],"mappings":"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","names":["sheet","next"]}
@@ -1898,6 +1898,10 @@ var SHEET_MUTATIONS = {
1898
1898
  "data-validation.mutation.updateRule": covered("dv", "ruleId", "payload", "source", "derived"),
1899
1899
  "data-validation.mutation.removeRule": covered("dv", "ruleId", "source", "derived"),
1900
1900
  "sheet.mutation.set-filter-range": covered("filter", "range", "derived"),
1901
+ // C5f: Univer's cell notes, keyed by note id, anchored to one cell (their transform: comment-anchor-transform.ts)
1902
+ "sheet.mutation.update-note": covered("note", "sheetId", "row", "col", "note", "silent", "derived"),
1903
+ "sheet.mutation.remove-note": covered("note", "sheetId", "noteId", "row", "col", "silent", "derived"),
1904
+ "sheet.mutation.update-note-position": covered("note", "sheetId", "noteId", "row", "col", "newPosition", "silent", "derived"),
1901
1905
  "sheet.mutation.set-filter-criteria": covered("filter", "col", "criteria", "reCalc", "derived"),
1902
1906
  "sheet.mutation.remove-filter": covered("filter", "derived"),
1903
1907
  "sheet.mutation.add-table": barrier("C5c", RESOURCE),
@@ -1910,9 +1914,6 @@ var SHEET_MUTATIONS = {
1910
1914
  "sheets.mutation.update-hyper-link": barrier("C5c", RESOURCE),
1911
1915
  "sheets.mutation.update-hyper-link-ref": barrier("C5c", RESOURCE),
1912
1916
  "sheets.mutation.update-rich-hyper-link": barrier("C5c", RESOURCE),
1913
- "sheet.mutation.update-note": barrier("C5c", RESOURCE),
1914
- "sheet.mutation.remove-note": barrier("C5c", RESOURCE),
1915
- "sheet.mutation.update-note-position": barrier("C5c", RESOURCE),
1916
1917
  "sheet.mutation.set-drawing-apply": barrier("C5c", RESOURCE),
1917
1918
  "sheet.mutation.set-worksheet-background-image": barrier("C5c", RESOURCE),
1918
1919
  "sheet.mutation.add-range-theme": barrier("C5c", RESOURCE),
@@ -2327,7 +2328,88 @@ var label = (m) => {
2327
2328
  const c = cellOf(m);
2328
2329
  return `${String(m.params.subUnitId ?? "")}!${c ? sheetCellName(c) : "?"} comment`;
2329
2330
  };
2331
+ var UPDATE_NOTE = "sheet.mutation.update-note";
2332
+ var REMOVE_NOTE = "sheet.mutation.remove-note";
2333
+ var UPDATE_NOTE_POSITION = "sheet.mutation.update-note-position";
2334
+ var NOTE_IDS = /* @__PURE__ */ new Set([UPDATE_NOTE, REMOVE_NOTE, UPDATE_NOTE_POSITION]);
2335
+ var isNoteMutation = (id) => NOTE_IDS.has(id);
2336
+ var cellAt = (row, col) => typeof row === "number" && typeof col === "number" ? { row, col } : null;
2337
+ function noteRef(m) {
2338
+ const p = m.params;
2339
+ const sheet = String(p.sheetId ?? "");
2340
+ if (m.id === UPDATE_NOTE) return { role: "write", sheet, id: p.note?.id ? String(p.note.id) : null, cell: cellAt(p.row, p.col) };
2341
+ if (m.id === UPDATE_NOTE_POSITION) return { role: "move", sheet, id: p.noteId ? String(p.noteId) : null, cell: cellAt(p.newPosition?.row, p.newPosition?.col) };
2342
+ return { role: "remove", sheet, id: p.noteId ? String(p.noteId) : null, cell: p.noteId ? null : cellAt(p.row, p.col) };
2343
+ }
2344
+ var noteDerived = (m) => m.params.derived === true;
2345
+ var noteLabel = (n) => `${n.sheet}!${n.cell ? sheetCellName(n.cell) : "?"} note`;
2346
+ var tellNote = (m, n) => noteDerived(m) ? [] : [noteLabel(n)];
2347
+ var noteBarrier = (reason) => ({ barrier: true, reason: `note: ${reason}` });
2348
+ var removeNoteOf = (sheet, id) => ({ id: REMOVE_NOTE, params: { sheetId: sheet, noteId: id, derived: true } });
2349
+ var moveNoteTo = (sheet, id, cell) => ({
2350
+ id: UPDATE_NOTE_POSITION,
2351
+ params: { sheetId: sheet, noteId: id, newPosition: { row: cell.row, col: cell.col }, derived: true }
2352
+ });
2353
+ var onSheet = (m, sheet) => String(m.params.subUnitId ?? "") === sheet;
2354
+ function transformCellNote(x, y, side) {
2355
+ const xn = isNoteMutation(x.id);
2356
+ const yn = isNoteMutation(y.id);
2357
+ if (!xn && !yn) return null;
2358
+ if (!xn) {
2359
+ const n2 = noteRef(y);
2360
+ if (n2.role === "remove" || !n2.cell || !onSheet(x, n2.sheet)) return ok([x]);
2361
+ if (x.id === REMOVE_SHEET) return n2.id ? ok([removeNoteOf(n2.sheet, n2.id), x]) : ok([x]);
2362
+ if (!structuralAxisOp(x.id, x.params)) return ok([x]);
2363
+ if (!n2.id) return noteBarrier("a note without an id against a row or column change");
2364
+ const moved = moveSheetAnchorCell(n2.cell, n2.sheet, x.id, x.params);
2365
+ if (!moved) return ok([x, removeNoteOf(n2.sheet, n2.id)]);
2366
+ return moved === n2.cell ? ok([x]) : ok([x, moveNoteTo(n2.sheet, n2.id, moved)]);
2367
+ }
2368
+ const n = noteRef(x);
2369
+ if (!yn) {
2370
+ if (!onSheet(y, n.sheet)) return ok([x]);
2371
+ if (y.id === REMOVE_SHEET) return ok([], [], tellNote(x, n));
2372
+ if (!structuralAxisOp(y.id, y.params)) return ok([x]);
2373
+ if (n.role === "write" && !n.id) return noteBarrier("a note without an id against a row or column change");
2374
+ const p = x.params;
2375
+ const from = cellAt(p.row, p.col);
2376
+ const fromMoved = from ? moveSheetAnchorCell(from, n.sheet, y.id, y.params) : null;
2377
+ if (n.role === "remove") {
2378
+ if (n.id) return ok([x]);
2379
+ return fromMoved ? ok([{ id: x.id, params: { ...p, row: fromMoved.row, col: fromMoved.col } }]) : ok([]);
2380
+ }
2381
+ const moved = n.cell ? moveSheetAnchorCell(n.cell, n.sheet, y.id, y.params) : null;
2382
+ if (n.role === "write") {
2383
+ if (!moved) return ok([], [], tellNote(x, n));
2384
+ return ok([moved === n.cell ? x : { id: x.id, params: { ...p, row: moved.row, col: moved.col } }]);
2385
+ }
2386
+ if (!moved) return n.id ? ok([removeNoteOf(n.sheet, n.id)], [], tellNote(x, n)) : ok([], [], tellNote(x, n));
2387
+ const params = { ...p, newPosition: { ...p.newPosition, row: moved.row, col: moved.col } };
2388
+ if (fromMoved) Object.assign(params, { row: fromMoved.row, col: fromMoved.col });
2389
+ return ok([{ id: x.id, params }]);
2390
+ }
2391
+ const m = noteRef(y);
2392
+ if (n.sheet !== m.sheet) return ok([x]);
2393
+ if (!n.id || !m.id) return noteBarrier("a note change addressed by cell against another note change on its sheet");
2394
+ if (n.id !== m.id) {
2395
+ const sameCell = n.cell && m.cell && n.cell.row === m.cell.row && n.cell.col === m.cell.col;
2396
+ return sameCell ? noteBarrier("two notes written to one cell at once") : ok([x]);
2397
+ }
2398
+ if (n.role === "remove") return ok([x]);
2399
+ if (m.role === "remove") return ok([], [], tellNote(x, n));
2400
+ if (n.role === "write" && m.role === "move") {
2401
+ return m.cell ? ok([{ id: x.id, params: { ...x.params, row: m.cell.row, col: m.cell.col } }]) : ok([x]);
2402
+ }
2403
+ if (n.role === "move" && m.role === "write") return ok([x]);
2404
+ const dx = noteDerived(x);
2405
+ const dy = noteDerived(y);
2406
+ if (dx && dy) return ok([x]);
2407
+ const xWins = dx !== dy ? !dx : side === "left";
2408
+ return xWins ? ok([x], tellNote(y, m)) : ok([], [], tellNote(x, n));
2409
+ }
2330
2410
  function transformCommentAnchor(x, y, side) {
2411
+ const note = transformCellNote(x, y, side);
2412
+ if (note) return note;
2331
2413
  const xa = x.id === SET_SHEET_COMMENT_ANCHOR;
2332
2414
  const ya = y.id === SET_SHEET_COMMENT_ANCHOR;
2333
2415
  if (!xa && !ya) return null;
@@ -2448,7 +2530,7 @@ registerRangedResource({
2448
2530
  });
2449
2531
 
2450
2532
  // src/collab/sheets/transform/transform.ts
2451
- var SHEET_TRANSFORMER_VERSION = "c5d-1";
2533
+ var SHEET_TRANSFORMER_VERSION = "c5f-1";
2452
2534
  var ID = {
2453
2535
  values: "sheet.mutation.set-range-values",
2454
2536
  insertRow: "sheet.mutation.insert-row",
@@ -2537,6 +2619,21 @@ function buildStructural(template, axis, op) {
2537
2619
  const start = op.kind === "insert" ? op.at : op.start;
2538
2620
  return { id: template.id, params: { ...p, range: { ...p.range, [a0]: start, [a1]: start + op.count - 1 } } };
2539
2621
  }
2622
+ function crossThrough(x, sy) {
2623
+ const [a0, a1] = S(sy.axis);
2624
+ const p = x.params;
2625
+ const params = { ...p };
2626
+ for (const k of ["range", "sourceRange", "targetRange"]) {
2627
+ const r = p[k];
2628
+ if (!r || typeof r[a0] !== "number" || typeof r[a1] !== "number") continue;
2629
+ const spans = spanThrough(sy.op, r[a0], r[a1]);
2630
+ if (!spans.length) continue;
2631
+ const lo = r[a0] === 0 ? 0 : Math.min(...spans.map(([l]) => l));
2632
+ const hi = Math.max(...spans.map(([, h]) => h));
2633
+ params[k] = { ...r, [a0]: lo, [a1]: hi };
2634
+ }
2635
+ return { id: x.id, params };
2636
+ }
2540
2637
  function univerMutationInfo(axis, op) {
2541
2638
  const FULL2 = { startRow: 0, endRow: Number.MAX_SAFE_INTEGER, startColumn: 0, endColumn: Number.MAX_SAFE_INTEGER };
2542
2639
  const start = op.kind === "insert" ? op.at : op.start;
@@ -3260,7 +3357,7 @@ function annotateSheetOpsInOrder(group, sheetOrder) {
3260
3357
  let structural = false;
3261
3358
  return group.map((m0) => {
3262
3359
  if (structuralOf(m0)) structural = true;
3263
- const m = structural && rangedSpecOf(m0.id) && !isDerived(m0) ? { id: m0.id, params: { ...m0.params, derived: true } } : m0;
3360
+ const m = structural && (rangedSpecOf(m0.id) || isNoteMutation(m0.id)) && !isDerived(m0) ? { id: m0.id, params: { ...m0.params, derived: true } } : m0;
3264
3361
  if (m.id === ID.insertSheet) {
3265
3362
  const index = typeof m.params.index === "number" ? Math.min(m.params.index, order.length) : order.length;
3266
3363
  order.splice(index, 0, sheetIdOf(m));
@@ -3507,7 +3604,7 @@ function transformMutation(x, y, side) {
3507
3604
  const np = numfmtPair(x, y, side, sx, sy);
3508
3605
  if (np) return np;
3509
3606
  if (sx && sy) {
3510
- if (sx.axis !== sy.axis) return ok2([x]);
3607
+ if (sx.axis !== sy.axis) return ok2([crossThrough(x, sy)]);
3511
3608
  const out = transformAxis(sx.op, sy.op, side);
3512
3609
  if (!out) return barrier2(`${x.id} over ${y.id}: moved blocks overlap in part, or the block would split`);
3513
3610
  const dropped = out.length === 0 && sx.op.kind === "move" ? [`${sheetOf(x)}: moved ${sx.axis}s`] : [];
@@ -3729,8 +3826,8 @@ async function sheetsStateHash(snapshot) {
3729
3826
  }
3730
3827
 
3731
3828
  // src/univer/collab/sheets-ot/room.ts
3732
- var SHEETS_COLLAB_PROTO = 3;
3733
- var SHEETS_READABLE_PROTOS = [2, SHEETS_COLLAB_PROTO];
3829
+ var SHEETS_COLLAB_PROTO = 4;
3830
+ var SHEETS_READABLE_PROTOS = [2, 3, SHEETS_COLLAB_PROTO];
3734
3831
  var SHEETS_EDIT_GROUP = "sheets.edit";
3735
3832
  var flip2 = (side) => side === "left" ? "right" : "left";
3736
3833
  var isNoopSheetOp = (op) => op == null || op.length === 0;