Skip to content

Commit a23128d

Browse files
authored
Merge pull request #3484 from superdoc-dev/caio-pizzol/SD-typecheck-user-unification
refactor(superdoc): unify User type and drain 4 user-method obligations (SD-673)
2 parents 3728071 + a7ffdcf commit a23128d

10 files changed

Lines changed: 136 additions & 82 deletions

File tree

β€Žpackages/superdoc/src/core/SuperDoc.tsβ€Ž

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ const DEFAULT_AWARENESS_PALETTE = Object.freeze([
6262
// declared as interfaces below.
6363
import type {
6464
AwarenessState,
65+
AwarenessUser,
6566
CollaborationProvider,
6667
Config,
6768
DocumentMode,
@@ -382,7 +383,7 @@ export class SuperDoc extends EventEmitter<SuperDocEventMap> {
382383
declare superdocId: string;
383384
declare comments: unknown[];
384385
declare socket: HocuspocusProviderWebsocket | null;
385-
declare user: User;
386+
declare user: AwarenessUser;
386387
declare _cleanupAwareness: (() => void) | null;
387388
declare _commentsCollabInitialized: boolean;
388389

β€Žpackages/superdoc/src/core/types/index.tsβ€Ž

Lines changed: 47 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import type {
2929
FontConfig,
3030
FontsResolvedPayload,
3131
ProofingProvider,
32+
User,
3233
} from '@superdoc/super-editor';
3334

3435
import type { SuperDoc as SuperDocClass } from '../SuperDoc.js';
@@ -47,32 +48,37 @@ export type NavigableAddress = SuperEditorNavigableAddress;
4748
/**
4849
* The current user of this superdoc.
4950
*
50-
* Every field is optional on input. `SuperDoc.#init` normalizes a missing
51-
* or partial `user` by spreading `DEFAULT_USER` over consumer input, so
52-
* `name` and `email` always have a value at runtime even when the
53-
* consumer omits them. The typedef stays open so consumers can pass
54-
* `{ name: 'Ada' }` without a typecheck failure.
51+
* Re-exported directly from `@superdoc/super-editor` so the public
52+
* consumer-facing `User` (re-exported again at `src/public/index.ts`)
53+
* and the internal `User` referenced by SuperDoc method signatures
54+
* (`addSharedUser(user: User)`, `removeSharedUser(...)`, etc.) are
55+
* the same symbol β€” not two structurally-similar declarations.
5556
*
56-
* Kept structurally compatible with the publicly re-exported
57-
* `User` from `@superdoc/super-editor` (also optional name/email).
57+
* Every field is optional on input. `SuperDoc.#init` normalizes a
58+
* missing or partial `user` by spreading `DEFAULT_USER` over consumer
59+
* input, so `name` and `email` always have a value at runtime even
60+
* when the consumer omits them.
61+
*
62+
* `User` does NOT carry the collab-awareness `color` field; that is on
63+
* the internal `AwarenessUser` (see below), assigned by SuperDoc's
64+
* `#assignUserColor()` after `#init`.
5865
*/
59-
export interface User {
60-
/** The user's name. */
61-
name?: string;
62-
/**
63-
* The user's email. May be `null` when the consumer did not provide an
64-
* email and SuperDoc fell back to the built-in default user; the runtime
65-
* has always exposed `null` here, so the typedef accepts it explicitly
66-
* rather than narrowing to `string`. Consumers must narrow before
67-
* performing string operations on this field.
68-
*/
69-
email?: string | null;
70-
/** The user's photo. */
71-
image?: string | null;
66+
export type { User } from '@superdoc/super-editor';
67+
68+
/**
69+
* Internal post-`#init` shape of the active user. Extends the public
70+
* `User` with the collab-awareness `color` field assigned by
71+
* `SuperDoc.#assignUserColor()` and read by the presence system. Not
72+
* part of the consumer-facing surface; consumers continue to pass
73+
* `User` via `Config.user`, and SuperDoc widens to `AwarenessUser`
74+
* internally once it has computed the color.
75+
*/
76+
export interface AwarenessUser extends User {
7277
/**
7378
* Awareness color for collaborative cursors. Auto-assigned from the
74-
* configured palette (or a default palette) when omitted, derived from a
75-
* hash of the user's identity so the assignment is stable across reloads.
79+
* configured palette (or a default palette) by `#assignUserColor`,
80+
* derived from a hash of the user's identity so the assignment is
81+
* stable across reloads.
7682
*/
7783
color?: string;
7884
}
@@ -96,9 +102,11 @@ export interface AwarenessState extends User {
96102
/** Yjs client identifier for the remote peer. */
97103
clientId?: number;
98104
/**
99-
* Color assigned by SuperDoc's presence system. Overrides
100-
* {@link User.color} when the presence system computes a stable
101-
* palette assignment for the remote peer.
105+
* Color assigned by SuperDoc's presence system. Spread onto the
106+
* awareness entry after the user fields, so it takes precedence
107+
* over any color the awareness user carried in (see
108+
* {@link AwarenessUser.color}). Used when the presence system
109+
* computes a stable palette assignment for the remote peer.
102110
*/
103111
color?: string;
104112
/** Application-specific fields spread from the awareness provider. */
@@ -1389,8 +1397,14 @@ export interface Config {
13891397
password?: string;
13901398
/** The documents to load β†’ soon to be deprecated. */
13911399
documents?: Document[];
1392-
/** The current user of this SuperDoc. */
1393-
user?: User;
1400+
/**
1401+
* The current user of this SuperDoc. Typed as `AwarenessUser` (an
1402+
* extension of `User` with the optional `color` field) so consumers
1403+
* can pass an explicit awareness color and have the runtime honor it
1404+
* as an override - `SuperDoc#assignUserColor()` skips its hash-based
1405+
* assignment when `user.color` is already set.
1406+
*/
1407+
user?: AwarenessUser;
13941408
/** All users of this SuperDoc (can be used for "@"-mentions). */
13951409
users?: User[];
13961410
/** Colors to use for user awareness. */
@@ -1608,8 +1622,12 @@ export interface InternalConfig extends Config {
16081622
documents: RuntimeDocument[];
16091623
/** Normalized to `{}` by `#init` if the consumer passes nothing or `undefined`. */
16101624
modules: Modules;
1611-
/** Spread of `DEFAULT_USER` over consumer input by `#init`; `name` always present. */
1612-
user: User;
1625+
/**
1626+
* Spread of `DEFAULT_USER` over consumer input by `#init`; `name`
1627+
* always present. Widened to `AwarenessUser` because `#assignUserColor`
1628+
* runs synchronously during init and writes `color` into this object.
1629+
*/
1630+
user: AwarenessUser;
16131631
/** Normalized to `{}` by `#init` if the consumer passes nothing or `undefined`. */
16141632
layoutEngineOptions: SuperDocLayoutEngineOptions;
16151633
}

β€Žpackages/superdoc/src/public/index.tsβ€Ž

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export { createTheme } from '../core/theme/create-theme.js';
5858

5959
// Type source: ./core/types/index.js
6060
export type { AwarenessState } from '../core/types/index.js';
61+
export type { AwarenessUser } from '../core/types/index.js';
6162
export type { BlockNavigationAddress } from '../core/types/index.js';
6263
export type { BookmarkAddress } from '../core/types/index.js';
6364
export type { CollaborationConfig } from '../core/types/index.js';

β€Žtests/consumer-typecheck/public-method-coverage-debt-snapshot.jsonβ€Ž

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
"knownUnmet": [
44
"addCommentsList:parameters",
55
"addCommentsList:returns",
6-
"addSharedUser:parameters",
7-
"addSharedUser:returns",
86
"canPerformPermission:parameters",
97
"canPerformPermission:returns",
108
"closeSurface:parameters",
@@ -23,8 +21,6 @@
2321
"openSurface:parameters",
2422
"openSurface:returns",
2523
"removeCommentsList:returns",
26-
"removeSharedUser:parameters",
27-
"removeSharedUser:returns",
2824
"requiredNumberOfEditors:returns",
2925
"save:returns",
3026
"scrollToComment:parameters",

β€Žtests/consumer-typecheck/snapshots/superdoc-root-classification.jsonβ€Ž

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
{
22
"generatedAt": "2026-05-19T11:33:50.546Z",
33
"summary": {
4-
"total": 204,
4+
"total": 205,
55
"byBucket": {
66
"legacy-root": 60,
77
"internal-candidate": 8,
8-
"supported-root": 136
8+
"supported-root": 137
99
},
1010
"byConfidence": {
1111
"high": 102,
12-
"medium": 100,
12+
"medium": 101,
1313
"low": 2
1414
}
1515
},
@@ -47,6 +47,17 @@
4747
"inEsm": false,
4848
"inCjs": false
4949
},
50+
{
51+
"name": "AwarenessUser",
52+
"bucket": "supported-root",
53+
"rationale": "Collaboration/awareness type defined in core/types/index.ts. Extends User with an optional `color` field for consumer-supplied awareness color; typed on Config.user so the runtime override in SuperDoc#assignUserColor() is consumer-typable.",
54+
"confidence": "medium",
55+
"source": "collab",
56+
"inDts": true,
57+
"inDcts": true,
58+
"inEsm": false,
59+
"inCjs": false
60+
},
5061
{
5162
"name": "BinaryData",
5263
"bucket": "supported-root",
@@ -754,7 +765,7 @@
754765
{
755766
"name": "FlowBlock",
756767
"bucket": "legacy-root",
757-
"rationale": "In LayoutState.blocks. Layout-engine raw type that must not appear in public .d.ts per package-boundaries.md:64 β€” already leaks via PE closure.",
768+
"rationale": "In LayoutState.blocks. Layout-engine raw type that must not appear in public .d.ts per package-boundaries.md:64 \u2014 already leaks via PE closure.",
758769
"confidence": "high",
759770
"source": "locked",
760771
"inDts": true,
@@ -842,7 +853,7 @@
842853
{
843854
"name": "Layout",
844855
"bucket": "legacy-root",
845-
"rationale": "In PE.onLayoutUpdated payload (LayoutState & { layout: Layout; ... }). Layout-engine raw type that must not appear in public .d.ts per package-boundaries.md:64 β€” already leaks via PE legacy API.",
856+
"rationale": "In PE.onLayoutUpdated payload (LayoutState & { layout: Layout; ... }). Layout-engine raw type that must not appear in public .d.ts per package-boundaries.md:64 \u2014 already leaks via PE legacy API.",
846857
"confidence": "high",
847858
"source": "locked",
848859
"inDts": true,

β€Žtests/consumer-typecheck/snapshots/superdoc-root-classification.mdβ€Ž

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,27 @@
11
# SD-3212 A1 β€” root classification
22

3-
Generated: 2026-05-19T11:33:50.546Z
4-
Input: tests/consumer-typecheck/snapshots/superdoc-root-exports.json (200 names, locked baseline)
3+
Generated: 2026-05-25T00:00:00.000Z
4+
Input: tests/consumer-typecheck/snapshots/superdoc-root-exports.json (205 names, locked baseline)
55

66
## Summary
77

88
| Bucket | Count |
99
|---|---|
10-
| supported-root | 132 |
10+
| supported-root | 137 |
1111
| legacy-root | 60 |
1212
| move-to-subpath | 0 |
1313
| internal-candidate | 8 |
1414
| NEEDS-REVIEW | 0 |
15-
| **total** | **200** |
15+
| **total** | **205** |
1616

17-
Confidence: high=98, medium=100, needs-review=0.
17+
Confidence: high=102, medium=101, needs-review=0.
1818

19-
## supported-root (132)
19+
## supported-root (137)
2020

2121
| Name | Confidence | Source | Rationale |
2222
|---|---|---|---|
2323
| `AwarenessState` | medium | collab | Collaboration/awareness type defined in core/types/index.ts. Customer-facing for collab-provider integrations (e.g., AwarenessState types the documented onAwarenessUpdate callback). |
24+
| `AwarenessUser` | medium | collab | Collaboration/awareness type defined in core/types/index.ts. Extends User with an optional `color` field for consumer-supplied awareness color; typed on Config.user so the runtime override in SuperDoc#assignUserColor() is consumer-typable. |
2425
| `BinaryData` | high | locked | Shape of binary content used in documented import/export/open/save paths. Type-reachable through documented APIs. |
2526
| `BlockNavigationAddress` | high | doc-api | Document API navigation/address/selection type. Promoted into the root facade by SD-3185. |
2627
| `BlocksListResult` | high | doc-api | Document API navigation/address/selection type. Promoted into the root facade by SD-3185. |

β€Žtests/consumer-typecheck/snapshots/superdoc-root-exports.jsonβ€Ž

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"generatedAt": "2026-05-23T12:40:30.787Z",
2+
"generatedAt": "2026-05-25T11:25:51.224Z",
33
"ticket": "SD-3212 PR A0",
44
"package": "superdoc",
55
"rootExport": {
@@ -17,6 +17,7 @@
1717
"AIWriter",
1818
"AnnotatorHelpers",
1919
"AwarenessState",
20+
"AwarenessUser",
2021
"BinaryData",
2122
"BlankDOCX",
2223
"BlockNavigationAddress",
@@ -227,6 +228,7 @@
227228
"AIWriter",
228229
"AnnotatorHelpers",
229230
"AwarenessState",
231+
"AwarenessUser",
230232
"BinaryData",
231233
"BlankDOCX",
232234
"BlockNavigationAddress",
@@ -527,11 +529,11 @@
527529
}
528530
},
529531
"counts": {
530-
"types.import": 204,
531-
"types.require": 204,
532+
"types.import": 205,
533+
"types.require": 205,
532534
"import": 41,
533535
"require": 41,
534-
"union": 204
536+
"union": 205
535537
},
536538
"divergences": {
537539
"typesImportVsRequire": {
@@ -545,6 +547,7 @@
545547
"typesVsRuntime": {
546548
"typedOnly": [
547549
"AwarenessState",
550+
"AwarenessUser",
548551
"BinaryData",
549552
"BlockNavigationAddress",
550553
"BlocksListResult",

β€Žtests/consumer-typecheck/snapshots/superdoc-root-exports.mdβ€Ž

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,31 @@
11
# superdoc root export inventory (SD-3212 PR A0)
22

3-
Generated: 2026-05-23T12:40:30.787Z
3+
Generated: 2026-05-25T11:25:51.224Z
44
Source: packed and installed `tests/consumer-typecheck/node_modules/superdoc`
55

66
## Counts
77

88
| Source | Path | Count |
99
|---|---|---|
10-
| types.import | `./dist/superdoc/src/public/index.d.ts` | 204 |
11-
| types.require | `./dist/superdoc/src/public/index.d.cts` | 204 |
10+
| types.import | `./dist/superdoc/src/public/index.d.ts` | 205 |
11+
| types.require | `./dist/superdoc/src/public/index.d.cts` | 205 |
1212
| import | `./dist/superdoc.es.js` | 41 |
1313
| require | `./dist/superdoc.cjs` | 41 |
14-
| **union** | | **204** |
14+
| **union** | | **205** |
1515

1616
## Divergences
1717

1818
- types.import only (not in types.require): 0
1919
- types.require only (not in types.import): 0
2020
- ESM only (not in CJS): 0
2121
- CJS only (not in ESM): 0
22-
- typed but no runtime export (phantom risk): 163
22+
- typed but no runtime export (phantom risk): 164
2323
- runtime export but not typed (silent shadow on root): 0
2424

2525
### Type-only names (no runtime)
2626

2727
- `AwarenessState`
28+
- `AwarenessUser`
2829
- `BinaryData`
2930
- `BlockNavigationAddress`
3031
- `BlocksListResult`
@@ -195,6 +196,7 @@ Source: packed and installed `tests/consumer-typecheck/node_modules/superdoc`
195196
| `AIWriter` | βœ“ | βœ“ | βœ“ | βœ“ | 1 | | 0 | 0 | 4 | |
196197
| `AnnotatorHelpers` | βœ“ | βœ“ | βœ“ | βœ“ | 1 | | 0 | 0 | 1 | |
197198
| `AwarenessState` | βœ“ | βœ“ | | | 3 | βœ“ | 0 | 0 | 0 | |
199+
| `AwarenessUser` | βœ“ | βœ“ | | | 0 | | 0 | 0 | 0 | |
198200
| `BinaryData` | βœ“ | βœ“ | | | 3 | βœ“ | 0 | 0 | 0 | |
199201
| `BlankDOCX` | βœ“ | βœ“ | βœ“ | βœ“ | 0 | | 0 | 0 | 1 | |
200202
| `BlockNavigationAddress` | βœ“ | βœ“ | | | 1 | βœ“ | 0 | 0 | 0 | |
@@ -228,7 +230,7 @@ Source: packed and installed `tests/consumer-typecheck/node_modules/superdoc`
228230
| `DirectSurfaceRequest` | βœ“ | βœ“ | | | 1 | βœ“ | 0 | 0 | 0 | |
229231
| `DocRange` | βœ“ | βœ“ | | | 1 | βœ“ | 0 | 0 | 0 | |
230232
| `DocumentApi` | βœ“ | βœ“ | | | 3 | βœ“ | 0 | 8 | 4 | βœ“ |
231-
| `DocumentMode` | βœ“ | βœ“ | | | 2 | βœ“ | 2 | 16 | 3 | |
233+
| `DocumentMode` | βœ“ | βœ“ | | | 3 | βœ“ | 2 | 16 | 3 | |
232234
| `DocumentProtectionState` | βœ“ | βœ“ | | | 1 | βœ“ | 1 | 0 | 1 | |
233235
| `DocxFileEntry` | βœ“ | βœ“ | | | 3 | βœ“ | 0 | 0 | 0 | |
234236
| `DocxZipper` | βœ“ | βœ“ | βœ“ | βœ“ | 2 | | 0 | 0 | 1 | βœ“ |
@@ -282,7 +284,7 @@ Source: packed and installed `tests/consumer-typecheck/node_modules/superdoc`
282284
| `ListDefinitionsPayload` | βœ“ | βœ“ | | | 1 | βœ“ | 0 | 0 | 0 | |
283285
| `Measure` | βœ“ | βœ“ | | | 3 | βœ“ | 0 | 0 | 1 | |
284286
| `Modules` | βœ“ | βœ“ | | | 1 | βœ“ | 4 | 0 | 0 | |
285-
| `NavigableAddress` | βœ“ | βœ“ | | | 1 | βœ“ | 0 | 0 | 0 | |
287+
| `NavigableAddress` | βœ“ | βœ“ | | | 2 | βœ“ | 0 | 0 | 0 | |
286288
| `OpenOptions` | βœ“ | βœ“ | | | 3 | βœ“ | 1 | 0 | 0 | |
287289
| `PDF` | βœ“ | βœ“ | βœ“ | βœ“ | 2 | | 35 | 0 | 1 | βœ“ |
288290
| `PageMargins` | βœ“ | βœ“ | | | 3 | βœ“ | 0 | 0 | 0 | |
@@ -339,11 +341,11 @@ Source: packed and installed `tests/consumer-typecheck/node_modules/superdoc`
339341
| `SlashMenu` | βœ“ | βœ“ | βœ“ | βœ“ | 1 | | 0 | 0 | 1 | |
340342
| `StoryLocator` | βœ“ | βœ“ | | | 1 | βœ“ | 116 | 0 | 3 | |
341343
| `SuperConverter` | βœ“ | βœ“ | βœ“ | βœ“ | 1 | | 0 | 0 | 3 | βœ“ |
342-
| `SuperDoc` | βœ“ | βœ“ | βœ“ | βœ“ | 11 | | 1014 | 180 | 244 | βœ“ |
343-
| `SuperDocExceptionEditorPayload` | βœ“ | βœ“ | | | 1 | | 0 | 0 | 0 | |
344-
| `SuperDocExceptionPayload` | βœ“ | βœ“ | | | 1 | | 0 | 0 | 0 | |
345-
| `SuperDocExceptionRestorePayload` | βœ“ | βœ“ | | | 0 | | 0 | 0 | 0 | |
346-
| `SuperDocExceptionStorePayload` | βœ“ | βœ“ | | | 1 | | 0 | 0 | 0 | |
344+
| `SuperDoc` | βœ“ | βœ“ | βœ“ | βœ“ | 13 | | 1014 | 180 | 244 | βœ“ |
345+
| `SuperDocExceptionEditorPayload` | βœ“ | βœ“ | | | 2 | | 0 | 0 | 0 | |
346+
| `SuperDocExceptionPayload` | βœ“ | βœ“ | | | 2 | | 0 | 0 | 0 | |
347+
| `SuperDocExceptionRestorePayload` | βœ“ | βœ“ | | | 1 | | 0 | 0 | 0 | |
348+
| `SuperDocExceptionStorePayload` | βœ“ | βœ“ | | | 2 | | 0 | 0 | 0 | |
347349
| `SuperDocLayoutEngineOptions` | βœ“ | βœ“ | | | 2 | βœ“ | 0 | 0 | 0 | |
348350
| `SuperDocTelemetryConfig` | βœ“ | βœ“ | | | 1 | βœ“ | 0 | 0 | 0 | |
349351
| `SuperEditor` | βœ“ | βœ“ | βœ“ | βœ“ | 1 | | 16 | 0 | 5 | |
@@ -371,7 +373,7 @@ Source: packed and installed `tests/consumer-typecheck/node_modules/superdoc`
371373
| `Transaction` | βœ“ | βœ“ | | | 3 | βœ“ | 5 | 0 | 0 | βœ“ |
372374
| `UnsupportedContentItem` | βœ“ | βœ“ | | | 3 | βœ“ | 0 | 0 | 0 | |
373375
| `UpgradeToCollaborationOptions` | βœ“ | βœ“ | | | 1 | βœ“ | 0 | 0 | 0 | |
374-
| `User` | βœ“ | βœ“ | | | 4 | βœ“ | 51 | 8 | 30 | |
376+
| `User` | βœ“ | βœ“ | | | 5 | βœ“ | 51 | 8 | 30 | |
375377
| `ViewLayout` | βœ“ | βœ“ | | | 1 | βœ“ | 0 | 0 | 0 | |
376378
| `ViewOptions` | βœ“ | βœ“ | | | 1 | βœ“ | 2 | 0 | 0 | |
377379
| `ViewingVisibilityConfig` | βœ“ | βœ“ | | | 1 | βœ“ | 0 | 0 | 0 | |

β€Žtests/consumer-typecheck/src/all-public-types.tsβ€Ž

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ import type {
101101
LinkPopoverResolution,
102102
LinkPopoverResolver,
103103
AwarenessState,
104+
AwarenessUser,
104105
ListDefinitionsPayload,
105106
Measure,
106107
Modules,
@@ -273,6 +274,7 @@ const _real_LinkPopoverContext: AssertNotAny<LinkPopoverContext> = true;
273274
const _real_LinkPopoverResolution: AssertNotAny<LinkPopoverResolution> = true;
274275
const _real_LinkPopoverResolver: AssertNotAny<LinkPopoverResolver> = true;
275276
const _real_AwarenessState: AssertNotAny<AwarenessState> = true;
277+
const _real_AwarenessUser: AssertNotAny<AwarenessUser> = true;
276278
const _real_ListDefinitionsPayload: AssertNotAny<ListDefinitionsPayload> = true;
277279
const _real_Measure: AssertNotAny<Measure> = true;
278280
const _real_Modules: AssertNotAny<Modules> = true;

0 commit comments

Comments
Β (0)