@lincyaw/kernel 1.0.0-rc.0 → 1.0.0-rc.1

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.
Files changed (119) hide show
  1. package/README.md +7 -7
  2. package/package.json +6 -2
  3. package/src/access.ts +18 -0
  4. package/src/anchors.ts +5 -1
  5. package/src/authoring.ts +56 -0
  6. package/src/blocks.ts +0 -8
  7. package/src/component-code.ts +6 -0
  8. package/src/contracts/agent.ts +5 -82
  9. package/src/contracts/anchors.ts +46 -0
  10. package/src/contracts/authoring-actions.ts +60 -0
  11. package/src/contracts/authoring-clients.ts +213 -0
  12. package/src/contracts/authoring.ts +180 -0
  13. package/src/contracts/checks.ts +29 -0
  14. package/src/contracts/classroom.ts +49 -0
  15. package/src/contracts/diagnostics.ts +37 -0
  16. package/src/contracts/document-audio.ts +150 -0
  17. package/src/contracts/document-comments.ts +31 -0
  18. package/src/contracts/document-history.ts +63 -0
  19. package/src/contracts/document-image.ts +32 -0
  20. package/src/contracts/document-reads.ts +57 -0
  21. package/src/contracts/document-voice.ts +44 -0
  22. package/src/contracts/document.ts +240 -0
  23. package/src/contracts/execution.ts +4 -5
  24. package/src/contracts/locale.ts +3 -0
  25. package/src/contracts/offline.ts +2 -0
  26. package/src/contracts/plugin.ts +183 -0
  27. package/src/contracts/presenter-panels.ts +17 -0
  28. package/src/contracts/preview.ts +305 -310
  29. package/src/contracts/production-assets.ts +61 -0
  30. package/src/contracts/production.ts +109 -0
  31. package/src/contracts/projects.ts +59 -0
  32. package/src/contracts/server.ts +1 -51
  33. package/src/contracts/speech-settings.ts +16 -0
  34. package/src/contracts/speech.ts +82 -70
  35. package/src/contracts/steps.ts +15 -0
  36. package/src/contracts/transcription-adoption.ts +79 -0
  37. package/src/contracts/transcription.ts +112 -0
  38. package/src/contracts/voice.ts +97 -0
  39. package/src/contracts/voiceover-execution.ts +68 -0
  40. package/src/contracts/workspace.ts +458 -0
  41. package/src/contracts.ts +28 -278
  42. package/src/diagnostics.ts +7 -0
  43. package/src/document-authoring-actions.ts +47 -0
  44. package/src/document-caption-timings.ts +64 -0
  45. package/src/document-colors.ts +25 -0
  46. package/src/document-comments.ts +118 -0
  47. package/src/document-components.ts +251 -0
  48. package/src/document-operations.ts +132 -0
  49. package/src/document-pages.ts +215 -0
  50. package/src/document-reads.ts +81 -0
  51. package/src/document-schema.ts +85 -0
  52. package/src/document-voices.ts +153 -0
  53. package/src/document.ts +252 -0
  54. package/src/durations.ts +7 -0
  55. package/src/errors.ts +8 -0
  56. package/src/execution.ts +5 -1
  57. package/src/fingerprint.ts +1 -1
  58. package/src/follow.ts +26 -30
  59. package/src/git.ts +1 -1
  60. package/src/index.ts +66 -13
  61. package/src/ir.ts +54 -5
  62. package/src/jupyter-kernel.ts +9 -0
  63. package/src/kernel.ts +38 -25
  64. package/src/keyboard.ts +43 -0
  65. package/src/locale.ts +13 -0
  66. package/src/lock.ts +4 -8
  67. package/src/logging.ts +1 -1
  68. package/src/{markdown.ts → lower-myst.ts} +2 -1
  69. package/src/node/assets.ts +23 -0
  70. package/src/node/authoring-actions.ts +50 -0
  71. package/src/node/authoring-clients.ts +14 -0
  72. package/src/node/document-audio.ts +22 -0
  73. package/src/node/document-history.ts +11 -0
  74. package/src/node/document-image.ts +8 -0
  75. package/src/node/document-reads.ts +10 -0
  76. package/src/node/document-voice.ts +7 -0
  77. package/src/node/ffmpeg.ts +29 -0
  78. package/src/node/file-version.ts +6 -0
  79. package/src/node/fonts.ts +22 -0
  80. package/src/node/index.ts +29 -133
  81. package/src/node/installed-packages.ts +11 -0
  82. package/src/node/lines.ts +24 -0
  83. package/src/node/locale.ts +7 -0
  84. package/src/node/network.ts +11 -0
  85. package/src/node/operator-speech.ts +91 -0
  86. package/src/node/packages.ts +74 -0
  87. package/src/node/paths.ts +7 -0
  88. package/src/node/projects.ts +7 -0
  89. package/src/node/resources.ts +86 -0
  90. package/src/node/revision.ts +6 -0
  91. package/src/node/run-directory.ts +70 -0
  92. package/src/node/transcription-adoption.ts +9 -0
  93. package/src/node/transcription.ts +10 -0
  94. package/src/node/voiceover-routes.ts +73 -0
  95. package/src/node/voiceover.ts +199 -0
  96. package/src/page-source.ts +359 -0
  97. package/src/parameters.ts +81 -0
  98. package/src/preview-edit.ts +18 -0
  99. package/src/preview-events.ts +57 -0
  100. package/src/preview-frame.ts +1220 -0
  101. package/src/props.ts +17 -0
  102. package/src/push-stream.ts +67 -0
  103. package/src/reading.ts +12 -7
  104. package/src/recording.ts +38 -0
  105. package/src/renderer-components.ts +30 -0
  106. package/src/responses.ts +49 -0
  107. package/src/run-outputs.ts +15 -0
  108. package/src/runner.py +3 -3
  109. package/src/services.ts +6 -6
  110. package/src/slides.ts +36 -0
  111. package/src/speech-words.ts +76 -0
  112. package/src/speech.ts +13 -0
  113. package/src/step-keys.ts +38 -0
  114. package/src/steps.ts +7 -0
  115. package/src/transcription-adoption.ts +105 -0
  116. package/src/uri-decoding.ts +8 -0
  117. package/src/urls.ts +13 -0
  118. package/src/views.ts +47 -0
  119. package/src/voice.ts +86 -0
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Presmium's shared kernel: the IR and the plugin contracts, the service registry, anchors and checks. Plain TypeScript, used by the compiler, the renderers, the IR runtime and every plugin.
4
4
 
5
- Part of [Presmium](https://github.com/Lincyaw/presmium), which turns lectures written in MyST Markdown into web pages, slides, Jupyter notebooks and narrated lectures.
5
+ Part of [Presmium](https://github.com/Lincyaw/presmium), which publishes structured teaching documents as web pages, slides, Jupyter notebooks and narrated lectures.
6
6
 
7
7
  ## Install
8
8
 
@@ -14,19 +14,19 @@ Plugins declare it as a peer dependency, so they share the application's copy:
14
14
 
15
15
  ## Entries
16
16
 
17
- - `@lincyaw/kernel`: the contracts (`PresmiumNodePlugin`, `PluginDefinition`, `DirectiveSpec`, `IRPass`, `Renderer`, `RenderContext`, `Check`, the service tokens), the IR types (`IRDocument` and its nodes), `KERNEL_VERSION`, `IR_VERSION`, and plain helpers such as `docBlocks`, `withProp`, `deckLocale`, `json` and `problem`.
18
- - `@lincyaw/kernel/contracts`, `@lincyaw/kernel/ir`: the contracts or the IR types alone.
19
- - `@lincyaw/kernel/reading`: plain functions that read compiled documents (`chapters`, `blockText`, `narrationAt`, `tokenize`).
20
- - `@lincyaw/kernel/node`: helpers for Node code: `copyPublished` (its other exports are internal).
17
+ - `@lincyaw/kernel`: the contracts (`PresmiumNodePlugin`, `PluginDefinition`, `DirectiveSpec`, `IRPass`, `Renderer`, `RenderContext`, `Check`, the service tokens), the canonical authoring types (`TeachingDocument`, `DocumentEditOperation`), `DOCUMENT_ENTRY`, `DOCUMENT_SCHEMA_VERSION`, `DOCUMENT_JSON_SCHEMA`, `validateDocument`, the derived IR types (`IRDocument` and its nodes), `KERNEL_VERSION`, `IR_VERSION`, and plain helpers such as `docBlocks`, `withProp`, `deckLocale`, `jsonResponse` and `problemResponse`.
18
+ - `@lincyaw/kernel/contracts`, `@lincyaw/kernel/ir`: the contracts (types, service tokens and the preview protocol's parsers) or the IR types alone; the helpers are only in `@lincyaw/kernel`.
19
+ - `@lincyaw/kernel/reading`: plain functions that read a compiled document, the IR (`chapters`, `blockText`, `narrationAt`, `tokenize`); they do no I/O.
20
+ - `@lincyaw/kernel/node`: helpers for Node code: `copyPublished` and `isInside` (its other exports are internal).
21
21
  - `@lincyaw/kernel/runner.py`: the shared Python runner of the executors.
22
22
 
23
23
  ## Versions
24
24
 
25
- `KERNEL_VERSION` is the version of the plugin contract, separate from the package version: 1.0.0 in Presmium 1.0, a minor version higher for each compatible addition to the contract, a major version (and a major version of the packages) for an incompatible change. Plugins declare `kernel: '^1.0.0'`, which any Presmium 1.x satisfies; a plugin whose range excludes it is not loaded. `IR_VERSION` is the version of the IR's JSON format.
25
+ `KERNEL_VERSION` is the version of the plugin contract, separate from the package version: 1.0.0 in Presmium 1.0, a minor version higher for each compatible addition to the contract, a major version (and a major version of the packages) for an incompatible change. Plugins declare `kernel: '^1.0.0'`, which any Presmium 1.x satisfies; a plugin whose range excludes it is not loaded. `DOCUMENT_SCHEMA_VERSION` versions the authoritative authoring document; `IR_VERSION` separately versions derived runtime snapshots. Source CAS versions and runtime snapshot IDs have separate lifecycles.
26
26
 
27
27
  ## Documentation
28
28
 
29
- - [Plugin guide](https://github.com/Lincyaw/presmium/blob/main/site/plugins/lecture.en.md)
29
+ - [Plugin guide](https://github.com/Lincyaw/presmium/blob/main/site/plugins/locales/en/lecture.json)
30
30
 
31
31
  ## License
32
32
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lincyaw/kernel",
3
- "version": "1.0.0-rc.0",
3
+ "version": "1.0.0-rc.1",
4
4
  "description": "Presmium's shared kernel: the IR and plugin contracts, the service registry, anchors and checks",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -33,8 +33,12 @@
33
33
  "src"
34
34
  ],
35
35
  "dependencies": {
36
+ "@cfworker/json-schema": "4.1.1",
36
37
  "@types/semver": "^7.8.0",
38
+ "fast-json-patch": "3.1.1",
39
+ "json-schema-library": "11.6.16",
37
40
  "parse5": "7.3.0",
38
- "semver": "^7.8.5"
41
+ "semver": "^7.8.5",
42
+ "proper-lockfile": "4.1.2"
39
43
  }
40
44
  }
package/src/access.ts ADDED
@@ -0,0 +1,18 @@
1
+ import type { ApprovalKind, Member, MemberRole } from './contracts.ts'
2
+
3
+ const RANK: Record<MemberRole, number> = { reader: 0, editor: 1, owner: 2 }
4
+
5
+ export const APPROVAL_KINDS: readonly ApprovalKind[] = ['operate', 'spend', 'write', 'agent']
6
+
7
+ /** The person who runs presmium dev, presmium serve or presmium-mcp on their own machine. */
8
+ export const LOCAL_AUTHOR: Member = { id: 'local', name: 'local author', roles: ['owner'] }
9
+
10
+ /** Whether the member has the role or one above it (reader, editor, owner). */
11
+ export function hasRole(member: Member, role: MemberRole): boolean {
12
+ return member.roles.some(own => RANK[own] >= RANK[role])
13
+ }
14
+
15
+ /** Whether a window that reports this view role shows the presentation: the presenter view or the audience's. */
16
+ export function isPresentingRole(role: string | undefined): boolean {
17
+ return role === 'presenter' || role === 'audience'
18
+ }
package/src/anchors.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { AnchorResolver, AnchorResolverRegistry, Resolution } from './contracts.ts'
1
+ import type { AnchorResolver, AnchorResolverRegistry, Resolution } from './contracts/anchors.ts'
2
2
  import { errorMessage } from './errors.ts'
3
3
 
4
4
  const SCHEME = /^[a-z][a-z0-9+.-]*$/i
@@ -52,3 +52,7 @@ export function createAnchorResolverRegistry(resolvers: AnchorResolver[] = []):
52
52
  for (const resolver of resolvers) registry.register(resolver)
53
53
  return registry
54
54
  }
55
+
56
+ export function isRenderable(resolution: Resolution): boolean {
57
+ return resolution.status === 'exact' || resolution.status === 'moved'
58
+ }
@@ -0,0 +1,56 @@
1
+ import { Validator } from '@cfworker/json-schema'
2
+ import type { Schema } from '@cfworker/json-schema'
3
+ import { DOCUMENT_JSON_SCHEMA } from './document-schema.ts'
4
+ import type { DocumentChange } from './contracts/workspace.ts'
5
+ import type { DocumentIssue } from './contracts/document.ts'
6
+ import { DOCUMENT_PAGE_CHANGE_APPLY_SCHEMA, DOCUMENT_PAGE_CHANGE_SCHEMA, DOCUMENT_PAGE_DUPLICATION_APPLY_SCHEMA, DOCUMENT_PAGE_DUPLICATION_SCHEMA } from './contracts/authoring.ts'
7
+ import type { DocumentPageChangeReference, DocumentPageChangeRequest, DocumentPageDuplicationReference, DocumentPageDuplicationRequest } from './contracts/authoring.ts'
8
+
9
+ const object = (properties: Record<string, Schema>, required = Object.keys(properties)): Schema => ({ type: 'object', properties, required, additionalProperties: false })
10
+ const ref = (name: string): Schema => ({ $ref: `#/$defs/${name}` })
11
+ const id: Schema = { type: 'string', minLength: 1, maxLength: 200 }
12
+ const index: Schema = { type: 'integer', minimum: 0 }
13
+ const operation = (type: string, fields: Record<string, Schema>, required = Object.keys(fields)) => object({ type: { const: type }, ...fields }, ['type', ...required])
14
+ const { $schema: _schema, $id: _id, $defs, ...documentSchema } = structuredClone(DOCUMENT_JSON_SCHEMA) as Exclude<Schema, boolean>
15
+
16
+ /** Shared strict request schema; nested values reuse the canonical document definitions. */
17
+ export const DOCUMENT_CHANGE_JSON_SCHEMA: Schema = {
18
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
19
+ $ref: '#/$defs/DocumentChange',
20
+ $defs: {
21
+ ...$defs,
22
+ TeachingDocument: documentSchema,
23
+ DocumentOperation: { oneOf: [
24
+ operation('insertBlock', { sectionId: id, parentId: id, index, block: ref('Block') }, ['sectionId', 'index', 'block']),
25
+ operation('moveBlock', { blockId: id, sectionId: id, parentId: id, index }, ['blockId', 'sectionId', 'index']),
26
+ operation('deleteBlock', { blockId: id }),
27
+ operation('updateText', { blockId: id, content: { type: 'array', items: ref('RichTextNode') } }),
28
+ operation('updateSection', { sectionId: id, title: { type: 'string' }, layout: ref('Layout') }, ['sectionId']),
29
+ operation('setStepState', { stepId: id, visuals: { type: 'array', items: ref('Visual') }, inherits: id }, ['stepId', 'visuals']),
30
+ operation('setNarration', { narration: ref('Narration'), stepId: id, index }, ['narration']),
31
+ operation('setTiming', { stepId: id, sectionId: id, value: ref('StepProduction') }, ['value']),
32
+ operation('replaceAsset', { asset: ref('Asset') }),
33
+ ] },
34
+ DocumentChange: {
35
+ ...object({ id, baseDocumentVersion: { type: 'string', minLength: 1, maxLength: 128 }, replacement: ref('TeachingDocument'), operations: { type: 'array', minItems: 1, maxItems: 1000, items: ref('DocumentOperation') }, fileChanges: { type: 'array', maxItems: 15, items: object({ path: { type: 'string', minLength: 1 }, content: { type: 'string' }, base: { type: ['string', 'null'] } }) } }, ['id', 'baseDocumentVersion']),
36
+ oneOf: [{ required: ['replacement'], not: { required: ['operations'] } }, { required: ['operations'], not: { required: ['replacement'] } }],
37
+ },
38
+ },
39
+ }
40
+
41
+ const validator = new Validator(structuredClone(DOCUMENT_CHANGE_JSON_SCHEMA), '2020-12', false)
42
+ const duplication = new Validator(structuredClone(DOCUMENT_PAGE_DUPLICATION_SCHEMA) as unknown as Schema, '2020-12', false)
43
+ const duplicationApply = new Validator(structuredClone(DOCUMENT_PAGE_DUPLICATION_APPLY_SCHEMA) as unknown as Schema, '2020-12', false)
44
+ const pageChange = new Validator(structuredClone(DOCUMENT_PAGE_CHANGE_SCHEMA) as unknown as Schema, '2020-12', false)
45
+ const pageChangeApply = new Validator(structuredClone(DOCUMENT_PAGE_CHANGE_APPLY_SCHEMA) as unknown as Schema, '2020-12', false)
46
+
47
+ export function validateDocumentPageDuplication(value: unknown): value is DocumentPageDuplicationRequest { return duplication.validate(value).valid }
48
+ export function validateDocumentPageDuplicationApply(value: unknown): value is DocumentPageDuplicationReference { return duplicationApply.validate(value).valid }
49
+ export function validateDocumentPageChange(value: unknown): value is DocumentPageChangeRequest { return pageChange.validate(value).valid }
50
+ export function validateDocumentPageChangeApply(value: unknown): value is DocumentPageChangeReference { return pageChangeApply.validate(value).valid }
51
+
52
+ export function validateDocumentChange(value: unknown): { change?: DocumentChange, issues: DocumentIssue[] } {
53
+ const checked = validator.validate(value)
54
+ const issues: DocumentIssue[] = checked.errors.slice(0, 100).map(error => ({ path: error.instanceLocation.replace(/^#/, ''), code: `schema.${error.keyword}`, message: error.error, severity: 'error' }))
55
+ return { ...checked.valid ? { change: value as DocumentChange } : {}, issues }
56
+ }
package/src/blocks.ts CHANGED
@@ -17,11 +17,3 @@ export function nodeMdast(node: IRNode): MdastNode[] {
17
17
  if (node.type === 'markdown') return [node.mdast]
18
18
  return node.type === 'component' && node.fallback ? [node.fallback] : []
19
19
  }
20
-
21
- export function withProp(props: Record<string, unknown>, path: string, value: unknown): Record<string, unknown> {
22
- const [key = '', ...rest] = path.split('.')
23
- if (rest.length === 0) return { ...props, [key]: value }
24
- const inner = props[key]
25
- const object = inner !== null && typeof inner === 'object' && !Array.isArray(inner) ? inner as Record<string, unknown> : {}
26
- return { ...props, [key]: withProp(object, rest.join('.'), value) }
27
- }
@@ -0,0 +1,6 @@
1
+ import type { CodeNode, ComponentNode } from './ir.ts'
2
+
3
+ /** The code block among a component's child blocks, e.g. the code of a run panel. */
4
+ export function componentCode(node: ComponentNode): CodeNode | undefined {
5
+ return node.children?.map(block => block.node).find((child): child is CodeNode => child.type === 'code')
6
+ }
@@ -1,6 +1,7 @@
1
1
  import type { Member } from './server.ts'
2
2
  import { defineService } from './service.ts'
3
3
  import type { ServiceToken } from './service.ts'
4
+ import type { Locale } from './locale.ts'
4
5
 
5
6
  export interface SourceRange { file: string, startLine: number, endLine: number }
6
7
 
@@ -9,6 +10,7 @@ export interface Focus {
9
10
  id: string
10
11
  from: 'source' | 'web' | 'slides'
11
12
  document: string
13
+ scope?: 'document' | 'workspace'
12
14
  source: SourceRange
13
15
  excerpt: string
14
16
  section?: string
@@ -21,6 +23,7 @@ export interface Focus {
21
23
  export interface FocusReport {
22
24
  id: string
23
25
  from: Focus['from']
26
+ scope?: Focus['scope']
24
27
  section?: string
25
28
  block?: string
26
29
  step?: number
@@ -68,86 +71,6 @@ export interface AgentTool {
68
71
  input: Record<string, unknown>
69
72
  kind: AgentToolKind
70
73
  run(input: Record<string, unknown>, ctx: AgentToolContext): Promise<unknown>
74
+ /** Text for the codes `run` throws as error messages, in the document's language; other messages pass on as they are. */
75
+ errors?: Record<Locale, Record<string, string>>
71
76
  }
72
-
73
- export type ReviewChangeKind = 'added' | 'modified' | 'deleted'
74
-
75
- export interface ReviewChange {
76
- path: string
77
- kind: ReviewChangeKind
78
- binary: boolean
79
- added: number
80
- removed: number
81
- diff?: string
82
- }
83
-
84
- export interface ReviewStep {
85
- id: string
86
- label: string
87
- tool?: string
88
- at: string
89
- changes: ReviewChange[]
90
- }
91
-
92
- export interface ChangeReview {
93
- readonly root: string
94
- readonly visibility: 'immediate' | 'on-commit'
95
- step(label: string, tool?: string): Promise<ReviewStep>
96
- steps(): readonly ReviewStep[]
97
- diff(): Promise<ReviewChange[]>
98
- restoreTo?(stepId: string): Promise<void>
99
- accept(reject?: string[]): Promise<string[]>
100
- rollback(): Promise<void>
101
- close(): Promise<void>
102
- }
103
-
104
- export interface ChangeReviewService {
105
- readonly engine: string
106
- open(options: { document: string, task: string, member: Member }): Promise<ChangeReview>
107
- }
108
-
109
- export const CHANGE_REVIEW = defineService<ChangeReviewService>('presmium.changeReview')
110
-
111
- export interface RevisionOrigin {
112
- kind: 'agent' | 'document' | 'editor'
113
- task?: string
114
- member?: Member
115
- }
116
-
117
- export interface Revision {
118
- id: string
119
- message: string
120
- author: string
121
- date: string
122
- origin: RevisionOrigin
123
- files: string[]
124
- revertedBy?: string
125
- }
126
-
127
- export type RevertResult = { ok: true, revision: Revision } | { ok: false, conflicts: string[] }
128
-
129
- export interface RevisionService {
130
- commit(change: { files: string[], message: string, origin: RevisionOrigin }): Promise<Revision | undefined>
131
- history(filter?: { document?: string, task?: string, limit?: number }): Promise<Revision[]>
132
- revert(id: string, member: Member): Promise<RevertResult>
133
- }
134
-
135
- export const REVISIONS = defineService<RevisionService>('presmium.revisions')
136
-
137
- export interface DocumentLockService {
138
- claim(document: string, holder: string): (() => void) | undefined
139
- holder(document: string): string | undefined
140
- }
141
-
142
- export const DOCUMENT_LOCKS = defineService<DocumentLockService>('presmium.documentLocks')
143
-
144
- export type DocumentFileChange =
145
- | { path: string, content: Uint8Array, executable?: boolean, at?: number }
146
- | { path: string, link: string }
147
- | { path: string, remove: true }
148
-
149
- export interface DocumentFiles {
150
- write(changes: DocumentFileChange[]): Promise<void>
151
- }
152
-
153
- export const DOCUMENT_FILES = defineService<DocumentFiles>('presmium.documentFiles')
@@ -0,0 +1,46 @@
1
+ import type { Unsubscribe } from './plugin.ts'
2
+
3
+ export type AnchorString = `${string}:${string}`
4
+
5
+ export type ResolutionStatus = 'exact' | 'moved' | 'fuzzy' | 'lost'
6
+
7
+ export interface ResolvedLocation {
8
+ file: string
9
+ startLine: number
10
+ endLine: number
11
+ text: string
12
+ }
13
+
14
+ export interface Resolution {
15
+ status: ResolutionStatus
16
+ location?: ResolvedLocation
17
+ fingerprint?: string
18
+ candidates?: ResolvedLocation[]
19
+ message?: string
20
+ }
21
+
22
+ export interface SourceReader {
23
+ read(file: string, ref?: string): Promise<string | undefined>
24
+ }
25
+
26
+ export interface RecordedAnchor {
27
+ fingerprint: string
28
+ file: string
29
+ startLine: number
30
+ }
31
+
32
+ export interface AnchorResolver {
33
+ scheme: string
34
+ resolve(anchor: AnchorString, src: SourceReader, recorded?: RecordedAnchor, ref?: string): Promise<Resolution>
35
+ }
36
+
37
+ export interface AnchorResolverRegistry {
38
+ register(resolver: AnchorResolver): Unsubscribe
39
+ resolve(anchor: AnchorString, src: SourceReader, recorded?: RecordedAnchor, ref?: string): Promise<Resolution>
40
+ schemes(): string[]
41
+ }
42
+
43
+ export interface AnchorLockFile {
44
+ version: 1
45
+ anchors: Record<string, RecordedAnchor>
46
+ }
@@ -0,0 +1,60 @@
1
+ import type { DocumentCaptionTimingAction } from '../document-caption-timings.ts'
2
+ import type { DocumentCommentAction, DocumentCommentEffect } from './document-comments.ts'
3
+ import type { JsonValue, VoiceSource } from './document.ts'
4
+ import type { DocumentVoiceImpact, DocumentVoiceScope } from '../document-voices.ts'
5
+ import type { CanonicalSource } from '../ir.ts'
6
+ import type { DocumentChange, DocumentChangeAnswer, RevisionOrigin } from './workspace.ts'
7
+ import type { DocumentChangePlan } from './authoring.ts'
8
+
9
+ export const DOCUMENT_ACTION_MAX_REQUEST_CHARS = 4096
10
+ export const DOCUMENT_ACTION_MAX_RESPONSE_CHARS = 7168
11
+ export const DOCUMENT_AUTHORING_ACTION_TYPES = ['comment.create', 'comment.reply', 'comment.update', 'comment.react', 'comment.resolve', 'comment.reopen', 'comment.delete', 'voice.set', 'component.parameters.patch', 'component.step-parameters.patch', 'component.step-parameters.reset', 'component.definition.use', 'caption.timing.set', 'caption.timing.reset'] as const
12
+ export const DOCUMENT_PARAMETER_MAX_CHANGES = 32
13
+ export const DOCUMENT_PARAMETER_MAX_PATH_DEPTH = 16
14
+ /** Paths belong only to this component's parameters; set replaces an array item or appends at its length. */
15
+ export type DocumentParameterChange = { op: 'set', path: string[], value: JsonValue } | { op: 'remove', path: string[] }
16
+ export type DocumentComponentAction =
17
+ | { type: 'component.parameters.patch', blockId: string, changes: DocumentParameterChange[] }
18
+ | { type: 'component.step-parameters.patch', blockId: string, stepId: string, changes: DocumentParameterChange[] }
19
+ | { type: 'component.step-parameters.reset', blockId: string, stepId: string }
20
+ | { type: 'component.definition.use', blockId: string, definitionId: string, changes?: DocumentParameterChange[] }
21
+ export interface DocumentComponentEffect {
22
+ blockId: string
23
+ sectionId: string
24
+ layer: 'base' | 'step' | 'definition'
25
+ stepId?: string
26
+ definitionId: string
27
+ previousDefinitionId?: string
28
+ changed: boolean
29
+ paths: string[][]
30
+ affected: { blocks: string[], steps: string[], pages: string[] }
31
+ overrides: { preserved: string[], materialized: string[], reset: string[] }
32
+ references: { before: string[], after: string[] }
33
+ }
34
+ export const DOCUMENT_COMPONENT_IMPACT_COLLECTIONS = ['affected.blocks', 'affected.steps', 'affected.pages', 'overrides.preserved', 'overrides.materialized', 'overrides.reset', 'references.before', 'references.after'] as const
35
+ export type DocumentComponentImpactCollection = typeof DOCUMENT_COMPONENT_IMPACT_COLLECTIONS[number]
36
+ export interface DocumentComponentImpactQuery extends DocumentAuthoringActionRead { collection: DocumentComponentImpactCollection, offset?: number }
37
+ export interface DocumentComponentImpactAnswer {
38
+ planId: string, digest: string, source: CanonicalSource, currentSource: CanonicalSource, state: 'current' | 'applied' | 'old'
39
+ collection: DocumentComponentImpactCollection, offset: number, total: number, entries: string[], nextOffset?: number, complete: boolean
40
+ }
41
+ /** New recording adoption belongs to the real-media service, never a raw settings action. */
42
+ export type DocumentAuthoringAction = DocumentCommentAction | DocumentComponentAction | DocumentCaptionTimingAction | { type: 'voice.set', scope: DocumentVoiceScope, source: Exclude<VoiceSource, { type: 'recording' }> | null, replaceOverrides?: boolean }
43
+ export interface DocumentAuthoringActionRequest { id: string, source: CanonicalSource, action: DocumentAuthoringAction }
44
+ export type DocumentAuthoringActionEffect = { kind: 'caption', value: { stepId: string, segments: number, changed: boolean, mode: 'manual' | 'automatic' } } | { kind: 'comment', value: DocumentCommentEffect } | { kind: 'voice', scope: DocumentVoiceScope['kind'], value: DocumentVoiceImpact, recordings: { preserved: number, removed: number } } | { kind: 'component', value: DocumentComponentEffect }
45
+ export interface DocumentAuthoringActionPlan extends DocumentChangePlan {
46
+ action: DocumentAuthoringAction
47
+ effect: DocumentAuthoringActionEffect
48
+ change: DocumentChange
49
+ }
50
+ export interface DocumentAuthoringActionReference { planId: string, source: CanonicalSource, digest: string, confirmDeletion?: true }
51
+ export interface DocumentAuthoringActionRead { planId: string, digest: string }
52
+ export interface DocumentAuthoringActionInspection { plan: DocumentAuthoringActionPlan, currentSource: CanonicalSource, state: 'current' | 'applied' | 'old' }
53
+ export interface AppliedDocumentAuthoringAction extends Omit<DocumentChangeAnswer, 'content' | 'issues' | 'commit'> {
54
+ commit?: { id: string, origin: RevisionOrigin['kind'] }
55
+ planId: string
56
+ digest: string
57
+ action: DocumentAuthoringAction['type']
58
+ effect: DocumentAuthoringActionEffect
59
+ verification: { saved: true, source: CanonicalSource, plannedVersion: string, actionStateVerified: true, compiled: 'not-requested', runtime: 'not-requested' }
60
+ }
@@ -0,0 +1,213 @@
1
+ import type { Schema } from '@cfworker/json-schema'
2
+ import type { CanonicalSource } from '../ir.ts'
3
+ import type { Member } from './server.ts'
4
+ import type { ProductionBinding, ProductionPlaybackState } from './production.ts'
5
+ import { PRODUCTION_PLAYBACK_RATES } from './production.ts'
6
+ import { DOCUMENT_PAGE_DUPLICATION_SCHEMA } from './authoring.ts'
7
+
8
+ export const AUTHORING_CLIENT_MAX_REQUEST_CHARS = 4096
9
+ export const AUTHORING_CLIENT_MAX_RESPONSE_CHARS = 7168
10
+ export const AUTHORING_CLIENT_MAX_CLIENTS = 16
11
+ export const AUTHORING_CLIENT_MAX_REQUESTS = 64
12
+ export const AUTHORING_CLIENT_REQUEST_TIMEOUT_MS = 30000
13
+ export const AUTHORING_CLIENT_ACK_MAX_BYTES = 32768
14
+ export const AUTHORING_CLIENT_EVENT_MAX_BYTES = 8 << 10
15
+ export const AUTHORING_CLIENT_ACTIONS = ['state.read', 'source.flush', 'preview.prepare', 'comment-drafts.inspect', 'comment-drafts.read', 'comment-drafts.restore', 'comment-drafts.discard', 'component-drafts.inspect', 'component-drafts.read', 'component-drafts.restore', 'component-drafts.discard', 'playback.pause', 'position.step', 'playback.play', 'playback.seek', 'playback.rate'] as const
16
+ export type AuthoringClientActionType = typeof AUTHORING_CLIENT_ACTIONS[number]
17
+ export type AuthoringClientAction =
18
+ | { type: 'state.read' }
19
+ | { type: 'source.flush' }
20
+ | { type: 'preview.prepare' }
21
+ | { type: 'comment-drafts.inspect', cursor?: { after: string, token: string }, limit?: number }
22
+ | { type: 'comment-drafts.read', draft: ClientCommentDraftReference, offset: number, maxChars?: number }
23
+ | { type: 'comment-drafts.restore', draft: ClientCommentDraftReference, composerId: string, expectedCurrent: ClientCommentDraftReference | null }
24
+ | { type: 'comment-drafts.discard', draft: ClientCommentDraftReference }
25
+ | { type: 'component-drafts.inspect', cursor?: { after: string, token: string }, limit?: number }
26
+ | { type: 'component-drafts.read', draft: ClientComponentDraftReference, offset: number, maxChars?: number }
27
+ | { type: 'component-drafts.restore', draft: ClientComponentDraftReference, expectedCurrent: ClientComponentDraftReference | null }
28
+ | { type: 'component-drafts.discard', draft: ClientComponentDraftReference, current: boolean }
29
+ | { type: 'playback.pause' }
30
+ | { type: 'position.step', sectionId: string, stepId: string }
31
+ | { type: 'playback.play', binding: ProductionBinding }
32
+ | { type: 'playback.seek', binding: ProductionBinding, timeMs: number }
33
+ | { type: 'playback.rate', binding: ProductionBinding, rate: number }
34
+
35
+ export interface ClientCommentDraftReference { id: string, revision: number, token: string }
36
+ export interface ClientComponentDraftReference { id: string, revision: number, token: string, blockId: string, stepId?: string }
37
+ /** No parameter values, baselines, workspace identity or member IDs enter this metadata. */
38
+ export interface ClientComponentDraftMetadata {
39
+ draft: ClientComponentDraftReference
40
+ definitionId: string
41
+ current: boolean
42
+ storage: 'durable' | 'session'
43
+ status: 'available' | 'pending' | 'session-only'
44
+ updatedAt: number
45
+ baseVersion: string
46
+ sourceCurrent: boolean
47
+ composing: boolean
48
+ reset: boolean
49
+ }
50
+ /** Explicit inspection deliberately excludes content, source signatures, comment baselines and pending values. */
51
+ export interface ClientCommentDraftMetadata {
52
+ draft: ClientCommentDraftReference
53
+ kind: 'create' | 'reply' | 'edit'
54
+ storage: 'durable' | 'session'
55
+ updatedAt: number
56
+ baseVersion: string
57
+ composing: boolean
58
+ pendingSubmission: boolean
59
+ ownersKnown: boolean
60
+ active: boolean
61
+ discardable: boolean
62
+ restore?: { composerId: string, expectedCurrent: ClientCommentDraftReference | null }
63
+ threadId?: string
64
+ commentId?: string
65
+ }
66
+ export type AuthoringClientActionResult =
67
+ | { type: 'comment-drafts.inspect', family: 'comment', coverage: 'stored-and-session' | 'session-only', skippedInvalid: number, token: string, drafts: ClientCommentDraftMetadata[], next?: { after: string, token: string } }
68
+ | { type: 'comment-drafts.read', family: 'comment', draft: ClientCommentDraftReference, storage: 'durable' | 'session', format: 'presmium-rich-text-json-v1', offset: number, endOffset: number, totalUtf16: number, totalUtf8: number, sha256: string, jsonText: string }
69
+ | { type: 'comment-drafts.restore', family: 'comment', draft: ClientCommentDraftReference, composerId: string, restored: ClientCommentDraftReference, preservedCurrent: ClientCommentDraftReference | null, targetState: 'current' | 'changed' }
70
+ | { type: 'comment-drafts.discard', family: 'comment', draft: ClientCommentDraftReference, discarded: true }
71
+ | { type: 'component-drafts.inspect', family: 'component', coverage: 'stored-and-session' | 'session-only', skippedInvalid: number, token: string, drafts: ClientComponentDraftMetadata[], next?: { after: string, token: string } }
72
+ | { type: 'component-drafts.read', family: 'component', draft: ClientComponentDraftReference, storage: 'durable' | 'session', format: 'presmium-component-parameters-json-v1', offset: number, endOffset: number, totalUtf16: number, totalUtf8: number, sha256: string, jsonText: string }
73
+ | { type: 'component-drafts.restore', family: 'component', draft: ClientComponentDraftReference, restored: ClientComponentDraftMetadata, preservedCurrent: ClientComponentDraftReference | null }
74
+ | { type: 'component-drafts.discard', family: 'component', draft: ClientComponentDraftReference, current: boolean, discarded: true }
75
+
76
+ /** Reports contain no editor text. Time is an observed sample, never a server-estimated playhead. */
77
+ export interface AuthoringClientSnapshot {
78
+ revision: number
79
+ source: CanonicalSource
80
+ view: 'document' | 'present'
81
+ editing: { dirty: boolean, saving: boolean, composing: boolean, conflicted: boolean, locked: boolean, loading: boolean }
82
+ interaction: string
83
+ position?: { sectionId: string, stepId?: string }
84
+ binding?: ProductionBinding
85
+ preview?: { status: 'unprepared' | 'preparing' | 'prepared' | 'unavailable', source?: CanonicalSource, irRevision?: string, projectRevision?: number }
86
+ playback?: ProductionPlaybackState
87
+ actions: AuthoringClientActionType[]
88
+ unsubmittedContent: 'not-inspected'
89
+ }
90
+ export interface AuthoringClientConnection { clientId: string, epoch: string, connectionId: string }
91
+ /** This credential belongs to the host-side transport; it is never returned by MCP client/state reads. */
92
+ export interface AuthoringClientLease extends AuthoringClientConnection { credential: string }
93
+ export interface AuthoringClientOpen { clientId: string, epoch: string, snapshot: AuthoringClientSnapshot }
94
+ export interface AuthoringClientDescriptor extends AuthoringClientConnection {
95
+ document: string
96
+ connected: boolean
97
+ observedAt: string
98
+ verification: 'reported-state'
99
+ snapshot: AuthoringClientSnapshot
100
+ }
101
+ export interface AuthoringClientList { document: string, clients: AuthoringClientDescriptor[], nextAfter?: string, unavailable: readonly string[] }
102
+ export interface AuthoringClientRequest {
103
+ clientId: string
104
+ epoch: string
105
+ requestId: string
106
+ source: CanonicalSource
107
+ expectedClientState: { connectionId: string, revision: number }
108
+ action: AuthoringClientAction
109
+ }
110
+ export type AuthoringClientRequestState = 'sent' | 'executing' | 'done' | 'refused' | 'failed' | 'expired'
111
+ export interface AuthoringClientRequestResult extends AuthoringClientConnection {
112
+ document: string
113
+ requestId: string
114
+ action: AuthoringClientActionType
115
+ source: CanonicalSource
116
+ state: AuthoringClientRequestState
117
+ admittedAt: string
118
+ expiresAt: string
119
+ observedAt?: string
120
+ snapshot?: AuthoringClientSnapshot
121
+ result?: AuthoringClientActionResult
122
+ error?: { code: string }
123
+ verification: 'not-acknowledged' | 'browser-acknowledged'
124
+ current: boolean
125
+ }
126
+ export type AuthoringClientEvent =
127
+ | { type: 'connected', connectionId: string }
128
+ | { type: 'request', request: AuthoringClientRequest, expiresAt: string }
129
+ | { type: 'cancel', requestId: string, connectionId: string }
130
+ export interface AuthoringClientReport { sequence: number, snapshot: AuthoringClientSnapshot }
131
+ export interface AuthoringClientAcknowledgement extends AuthoringClientReport {
132
+ requestId: string
133
+ state: 'executing' | 'done' | 'refused' | 'failed'
134
+ error?: { code: string }
135
+ result?: AuthoringClientActionResult
136
+ }
137
+ export interface AuthoringClients {
138
+ list(document: string, query: { clientId?: string, after?: string }, member: Member): Promise<AuthoringClientList>
139
+ request(document: string, request: AuthoringClientRequest, member: Member): Promise<AuthoringClientRequestResult>
140
+ get(document: string, query: { clientId: string, epoch: string, requestId: string }, member: Member): Promise<AuthoringClientRequestResult>
141
+ }
142
+
143
+ /** Reserve the complete receipt and the host-only lease, rather than budgeting only the text fragment. */
144
+ export function authoringClientResultFits(context: { request: AuthoringClientRequest, connection: AuthoringClientConnection, document: string, snapshot: AuthoringClientSnapshot }, result: AuthoringClientActionResult): boolean {
145
+ const { request, document, snapshot } = context, time = '2000-01-01T00:00:00.000Z'
146
+ const connection = { clientId: context.connection.clientId, epoch: context.connection.epoch, connectionId: context.connection.connectionId }
147
+ const receipt: AuthoringClientRequestResult = { ...connection, document, requestId: request.requestId, action: request.action.type, source: request.source,
148
+ state: 'done', admittedAt: time, expiresAt: time, observedAt: time, snapshot, result, verification: 'browser-acknowledged', current: false }
149
+ const body = { connection: { ...connection, credential: '0'.repeat(100) }, report: { sequence: Number.MAX_SAFE_INTEGER, snapshot, result, requestId: request.requestId, state: 'done' } }
150
+ const wire = JSON.stringify(body)
151
+ const frame = JSON.stringify({ type: 'authoring-client-publish', id: '0'.repeat(80), connectionId: connection.connectionId, sequence: Number.MAX_SAFE_INTEGER, kind: 'ack', value: body.report })
152
+ // Clock samples can change without changing the client revision; recheck the actual snapshot before sending.
153
+ return JSON.stringify(receipt, null, 2).length <= AUTHORING_CLIENT_MAX_RESPONSE_CHARS - 128
154
+ && wire.length <= AUTHORING_CLIENT_MAX_RESPONSE_CHARS - 128 && new TextEncoder().encode(wire).byteLength <= AUTHORING_CLIENT_ACK_MAX_BYTES
155
+ && new TextEncoder().encode(frame).byteLength <= AUTHORING_CLIENT_EVENT_MAX_BYTES - 128
156
+ }
157
+
158
+ const id: Schema = { type: 'string', minLength: 1, maxLength: 80, pattern: '^[a-zA-Z0-9.-]+$' }
159
+ const entity: Schema = { type: 'string', minLength: 1, maxLength: 200 }
160
+ const integer: Schema = { type: 'integer', minimum: 0, maximum: Number.MAX_SAFE_INTEGER }
161
+ const number: Schema = { type: 'number', minimum: 0 }
162
+ const bool: Schema = { type: 'boolean' }
163
+ const object = (properties: Record<string, Schema>, required = Object.keys(properties)): Schema => ({ type: 'object', additionalProperties: false, properties, required })
164
+ const digest: Schema = { type: 'string', pattern: '^[a-f0-9]{64}$' }
165
+ const draftReference = object({ id: entity, revision: { type: 'integer', minimum: 1, maximum: Number.MAX_SAFE_INTEGER }, token: digest })
166
+ const componentDraftReference = object({ id: entity, revision: { type: 'integer', minimum: 1, maximum: Number.MAX_SAFE_INTEGER }, token: digest, blockId: entity, stepId: entity }, ['id', 'revision', 'token', 'blockId'])
167
+ const componentMetadata = object({ draft: componentDraftReference, definitionId: entity, current: bool, storage: { enum: ['durable', 'session'] }, status: { enum: ['available', 'pending', 'session-only'] }, updatedAt: integer, baseVersion: entity, sourceCurrent: bool, composing: bool, reset: bool })
168
+ const nullableComponentReference: Schema = { oneOf: [componentDraftReference, { type: 'null' }] }
169
+ const nullableCommentReference: Schema = { oneOf: [draftReference, { type: 'null' }] }
170
+ const commentRestore = object({ composerId: id, expectedCurrent: nullableCommentReference })
171
+ const cursor = object({ after: entity, token: digest })
172
+ export const AUTHORING_CLIENT_ACTION_RESULT_SCHEMA: Schema = { oneOf: [
173
+ object({ type: { const: 'comment-drafts.inspect' }, family: { const: 'comment' }, coverage: { enum: ['stored-and-session', 'session-only'] }, skippedInvalid: integer,
174
+ token: digest, drafts: { type: 'array', maxItems: 10, items: object({ draft: draftReference, kind: { enum: ['create', 'reply', 'edit'] }, storage: { enum: ['durable', 'session'] }, updatedAt: integer, baseVersion: entity, composing: bool, pendingSubmission: bool, ownersKnown: bool, active: bool, discardable: bool, restore: commentRestore, threadId: entity, commentId: entity }, ['draft', 'kind', 'storage', 'updatedAt', 'baseVersion', 'composing', 'pendingSubmission', 'ownersKnown', 'active', 'discardable']) }, next: cursor }, ['type', 'family', 'coverage', 'skippedInvalid', 'token', 'drafts']),
175
+ object({ type: { const: 'comment-drafts.read' }, family: { const: 'comment' }, draft: draftReference, storage: { enum: ['durable', 'session'] }, format: { const: 'presmium-rich-text-json-v1' }, offset: integer, endOffset: integer, totalUtf16: integer, totalUtf8: integer, sha256: digest, jsonText: { type: 'string', maxLength: 2048 } }),
176
+ object({ type: { const: 'comment-drafts.restore' }, family: { const: 'comment' }, draft: draftReference, composerId: id, restored: draftReference, preservedCurrent: nullableCommentReference, targetState: { enum: ['current', 'changed'] } }),
177
+ object({ type: { const: 'comment-drafts.discard' }, family: { const: 'comment' }, draft: draftReference, discarded: { const: true } }),
178
+ object({ type: { const: 'component-drafts.inspect' }, family: { const: 'component' }, coverage: { enum: ['stored-and-session', 'session-only'] }, skippedInvalid: integer,
179
+ token: digest, drafts: { type: 'array', maxItems: 10, items: componentMetadata }, next: cursor }, ['type', 'family', 'coverage', 'skippedInvalid', 'token', 'drafts']),
180
+ object({ type: { const: 'component-drafts.read' }, family: { const: 'component' }, draft: componentDraftReference, storage: { enum: ['durable', 'session'] }, format: { const: 'presmium-component-parameters-json-v1' }, offset: integer, endOffset: integer, totalUtf16: integer, totalUtf8: integer, sha256: digest, jsonText: { type: 'string', maxLength: 2048 } }),
181
+ object({ type: { const: 'component-drafts.restore' }, family: { const: 'component' }, draft: componentDraftReference, restored: componentMetadata, preservedCurrent: nullableComponentReference }),
182
+ object({ type: { const: 'component-drafts.discard' }, family: { const: 'component' }, draft: componentDraftReference, current: bool, discarded: { const: true } }),
183
+ ] }
184
+ const source = DOCUMENT_PAGE_DUPLICATION_SCHEMA.properties.source as unknown as Schema
185
+ const binding = object({ source, irRevision: entity, planId: entity })
186
+ const position = object({ timeMs: number, stepTimeMs: number, sectionId: entity, stepId: entity, durationMs: number, playing: bool, rate: { type: 'number', exclusiveMinimum: 0, maximum: 16 }, trying: bool, waitingFor: entity }, ['timeMs', 'durationMs', 'playing', 'rate', 'trying'])
187
+ const playback = object({ binding, status: { enum: ['loading', 'ready', 'estimated', 'unavailable', 'failed'] }, position, error: { type: 'string', maxLength: 300 } }, ['binding', 'status'])
188
+ export const AUTHORING_CLIENT_SNAPSHOT_SCHEMA: Schema = object({
189
+ revision: integer, source, view: { enum: ['document', 'present'] }, editing: object({ dirty: bool, saving: bool, composing: bool, conflicted: bool, locked: bool, loading: bool }),
190
+ interaction: { type: 'string', maxLength: 200 }, position: object({ sectionId: entity, stepId: entity }, ['sectionId']), binding, playback,
191
+ preview: { oneOf: [object({ status: { enum: ['unprepared', 'preparing', 'unavailable'] } }), object({ status: { const: 'prepared' }, source, irRevision: entity, projectRevision: integer })] },
192
+ actions: { type: 'array', items: { enum: [...AUTHORING_CLIENT_ACTIONS] }, maxItems: AUTHORING_CLIENT_ACTIONS.length, uniqueItems: true }, unsubmittedContent: { const: 'not-inspected' },
193
+ }, ['revision', 'source', 'view', 'editing', 'interaction', 'actions', 'unsubmittedContent'])
194
+ const action = (type: string, properties: Record<string, Schema> = {}): Schema => object({ type: { const: type }, ...properties })
195
+ export const AUTHORING_CLIENT_ACTION_SCHEMA: Schema = { oneOf: [
196
+ action('state.read'), action('source.flush'), action('preview.prepare'), action('playback.pause'), action('position.step', { sectionId: entity, stepId: entity }),
197
+ object({ type: { const: 'comment-drafts.inspect' }, cursor, limit: { type: 'integer', minimum: 1, maximum: 10 } }, ['type']),
198
+ object({ type: { const: 'comment-drafts.read' }, draft: draftReference, offset: integer, maxChars: { type: 'integer', minimum: 2, maximum: 2048 } }, ['type', 'draft', 'offset']),
199
+ action('comment-drafts.restore', { draft: draftReference, composerId: id, expectedCurrent: nullableCommentReference }),
200
+ action('comment-drafts.discard', { draft: draftReference }),
201
+ object({ type: { const: 'component-drafts.inspect' }, cursor, limit: { type: 'integer', minimum: 1, maximum: 10 } }, ['type']),
202
+ object({ type: { const: 'component-drafts.read' }, draft: componentDraftReference, offset: integer, maxChars: { type: 'integer', minimum: 2, maximum: 2048 } }, ['type', 'draft', 'offset']),
203
+ action('component-drafts.restore', { draft: componentDraftReference, expectedCurrent: nullableComponentReference }),
204
+ action('component-drafts.discard', { draft: componentDraftReference, current: bool }),
205
+ action('playback.play', { binding }), action('playback.seek', { binding, timeMs: number }), action('playback.rate', { binding, rate: { enum: [...PRODUCTION_PLAYBACK_RATES] } }),
206
+ ] }
207
+ export const AUTHORING_CLIENT_REQUEST_SCHEMA: Schema = object({ clientId: id, epoch: id, requestId: id, source, expectedClientState: object({ connectionId: id, revision: integer }), action: AUTHORING_CLIENT_ACTION_SCHEMA })
208
+ export const AUTHORING_CLIENT_QUERY_SCHEMA: Schema = object({ clientId: id, after: id }, [])
209
+ export const AUTHORING_CLIENT_REQUEST_QUERY_SCHEMA: Schema = object({ clientId: id, epoch: id, requestId: id })
210
+ export const AUTHORING_CLIENT_OPEN_SCHEMA: Schema = object({ clientId: id, epoch: id, snapshot: AUTHORING_CLIENT_SNAPSHOT_SCHEMA })
211
+ export const AUTHORING_CLIENT_REPORT_SCHEMA: Schema = object({ sequence: integer, snapshot: AUTHORING_CLIENT_SNAPSHOT_SCHEMA })
212
+ export const AUTHORING_CLIENT_ACK_SCHEMA: Schema = object({ sequence: integer, snapshot: AUTHORING_CLIENT_SNAPSHOT_SCHEMA, requestId: id, state: { enum: ['executing', 'done', 'refused', 'failed'] }, error: object({ code: { type: 'string', minLength: 1, maxLength: 160 } }), result: AUTHORING_CLIENT_ACTION_RESULT_SCHEMA }, ['sequence', 'snapshot', 'requestId', 'state'])
213
+ export const AUTHORING_CLIENT_CONNECTION_SCHEMA: Schema = object({ clientId: id, epoch: id, connectionId: id, credential: { type: 'string', minLength: 32, maxLength: 100 } })