@lincyaw/kernel 0.0.0-stage → 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 (128) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +32 -2
  3. package/package.json +41 -3
  4. package/src/access.ts +18 -0
  5. package/src/anchors.ts +58 -0
  6. package/src/authoring.ts +56 -0
  7. package/src/blocks.ts +19 -0
  8. package/src/checks.ts +35 -0
  9. package/src/component-code.ts +6 -0
  10. package/src/contracts/agent.ts +76 -0
  11. package/src/contracts/anchors.ts +46 -0
  12. package/src/contracts/authoring-actions.ts +60 -0
  13. package/src/contracts/authoring-clients.ts +213 -0
  14. package/src/contracts/authoring.ts +180 -0
  15. package/src/contracts/checks.ts +29 -0
  16. package/src/contracts/classroom.ts +49 -0
  17. package/src/contracts/diagnostics.ts +37 -0
  18. package/src/contracts/document-audio.ts +150 -0
  19. package/src/contracts/document-comments.ts +31 -0
  20. package/src/contracts/document-history.ts +63 -0
  21. package/src/contracts/document-image.ts +32 -0
  22. package/src/contracts/document-reads.ts +57 -0
  23. package/src/contracts/document-voice.ts +44 -0
  24. package/src/contracts/document.ts +240 -0
  25. package/src/contracts/execution.ts +118 -0
  26. package/src/contracts/locale.ts +3 -0
  27. package/src/contracts/offline.ts +2 -0
  28. package/src/contracts/operations.ts +8 -0
  29. package/src/contracts/plugin.ts +183 -0
  30. package/src/contracts/presenter-panels.ts +17 -0
  31. package/src/contracts/preview.ts +386 -0
  32. package/src/contracts/production-assets.ts +61 -0
  33. package/src/contracts/production.ts +109 -0
  34. package/src/contracts/projects.ts +59 -0
  35. package/src/contracts/server.ts +108 -0
  36. package/src/contracts/service.ts +8 -0
  37. package/src/contracts/speech-settings.ts +16 -0
  38. package/src/contracts/speech.ts +149 -0
  39. package/src/contracts/steps.ts +15 -0
  40. package/src/contracts/transcription-adoption.ts +79 -0
  41. package/src/contracts/transcription.ts +112 -0
  42. package/src/contracts/voice.ts +97 -0
  43. package/src/contracts/voiceover-execution.ts +68 -0
  44. package/src/contracts/workspace.ts +458 -0
  45. package/src/contracts.ts +35 -0
  46. package/src/diagnostics.ts +7 -0
  47. package/src/document-authoring-actions.ts +47 -0
  48. package/src/document-caption-timings.ts +64 -0
  49. package/src/document-colors.ts +25 -0
  50. package/src/document-comments.ts +118 -0
  51. package/src/document-components.ts +251 -0
  52. package/src/document-operations.ts +132 -0
  53. package/src/document-pages.ts +215 -0
  54. package/src/document-reads.ts +81 -0
  55. package/src/document-schema.ts +85 -0
  56. package/src/document-voices.ts +153 -0
  57. package/src/document.ts +252 -0
  58. package/src/durations.ts +7 -0
  59. package/src/errors.ts +15 -0
  60. package/src/events.ts +32 -0
  61. package/src/execution.ts +25 -0
  62. package/src/fingerprint.ts +31 -0
  63. package/src/follow.ts +33 -0
  64. package/src/git.ts +12 -0
  65. package/src/html.ts +41 -0
  66. package/src/index.ts +82 -0
  67. package/src/ir.ts +367 -0
  68. package/src/jupyter-kernel.ts +9 -0
  69. package/src/kernel.ts +190 -0
  70. package/src/keyboard.ts +43 -0
  71. package/src/locale.ts +13 -0
  72. package/src/lock.ts +81 -0
  73. package/src/logging.ts +11 -0
  74. package/src/lower-myst.ts +312 -0
  75. package/src/node/assets.ts +23 -0
  76. package/src/node/authoring-actions.ts +50 -0
  77. package/src/node/authoring-clients.ts +14 -0
  78. package/src/node/document-audio.ts +22 -0
  79. package/src/node/document-history.ts +11 -0
  80. package/src/node/document-image.ts +8 -0
  81. package/src/node/document-reads.ts +10 -0
  82. package/src/node/document-voice.ts +7 -0
  83. package/src/node/ffmpeg.ts +29 -0
  84. package/src/node/file-version.ts +6 -0
  85. package/src/node/fonts.ts +22 -0
  86. package/src/node/index.ts +29 -0
  87. package/src/node/installed-packages.ts +11 -0
  88. package/src/node/lines.ts +24 -0
  89. package/src/node/locale.ts +7 -0
  90. package/src/node/network.ts +11 -0
  91. package/src/node/operator-speech.ts +91 -0
  92. package/src/node/packages.ts +74 -0
  93. package/src/node/paths.ts +7 -0
  94. package/src/node/projects.ts +7 -0
  95. package/src/node/resources.ts +86 -0
  96. package/src/node/revision.ts +6 -0
  97. package/src/node/run-directory.ts +70 -0
  98. package/src/node/strip-types.mjs +15 -0
  99. package/src/node/transcription-adoption.ts +9 -0
  100. package/src/node/transcription.ts +10 -0
  101. package/src/node/voiceover-routes.ts +73 -0
  102. package/src/node/voiceover.ts +199 -0
  103. package/src/page-source.ts +359 -0
  104. package/src/parameters.ts +81 -0
  105. package/src/preview-edit.ts +18 -0
  106. package/src/preview-events.ts +57 -0
  107. package/src/preview-frame.ts +1220 -0
  108. package/src/props.ts +17 -0
  109. package/src/published.ts +11 -0
  110. package/src/push-stream.ts +67 -0
  111. package/src/reading.ts +96 -0
  112. package/src/recording.ts +38 -0
  113. package/src/renderer-components.ts +30 -0
  114. package/src/requests.ts +28 -0
  115. package/src/responses.ts +49 -0
  116. package/src/run-outputs.ts +15 -0
  117. package/src/runner.py +454 -0
  118. package/src/services.ts +146 -0
  119. package/src/slides.ts +36 -0
  120. package/src/speech-words.ts +76 -0
  121. package/src/speech.ts +13 -0
  122. package/src/step-keys.ts +38 -0
  123. package/src/steps.ts +7 -0
  124. package/src/transcription-adoption.ts +105 -0
  125. package/src/uri-decoding.ts +8 -0
  126. package/src/urls.ts +13 -0
  127. package/src/views.ts +47 -0
  128. package/src/voice.ts +86 -0
@@ -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 } })
@@ -0,0 +1,180 @@
1
+ import type { CanonicalSource } from '../ir.ts'
2
+ import type { DocumentEditOperation, DocumentIssue, TeachingDocument } from './document.ts'
3
+ import type { Member } from './server.ts'
4
+ import type { DocumentAnswer, DocumentChange, DocumentChangeAnswer, RevisionOrigin } from './workspace.ts'
5
+ import type { DocumentOutlineAnswer, DocumentOutlineQuery, DocumentPartAnswer, DocumentPartQuery } from './document-reads.ts'
6
+ import type { AppliedDocumentAuthoringAction, DocumentAuthoringActionInspection, DocumentAuthoringActionPlan, DocumentAuthoringActionRead, DocumentAuthoringActionReference, DocumentAuthoringActionRequest, DocumentAuthoringAction, DocumentComponentImpactAnswer, DocumentComponentImpactQuery } from './authoring-actions.ts'
7
+
8
+ export const AUTHORING_API_VERSION = 1 as const
9
+ export const DOCUMENT_OPERATION_TYPES = ['insertBlock', 'moveBlock', 'deleteBlock', 'updateText', 'updateSection', 'setStepState', 'setNarration', 'setTiming', 'replaceAsset'] as const satisfies readonly DocumentEditOperation['type'][]
10
+
11
+ export interface AuthoringCapabilities {
12
+ apiVersion: typeof AUTHORING_API_VERSION
13
+ documentSchemaVersion: number
14
+ available: boolean
15
+ reason?: 'host-unavailable' | 'permission-denied'
16
+ actions: { read: boolean, outline: boolean, readPart: boolean, plan: boolean, apply: boolean, planPageDuplication: boolean, applyPageDuplication: boolean, planPageChange: boolean, applyPageChange: boolean, planAction: boolean, applyAction: boolean }
17
+ applyUnavailableReason?: 'review-required' | 'operation-disabled'
18
+ readLimits?: { maxRequestChars: number, maxResponseChars: number, responseSerialization: 'JSON.stringify(value,null,2)', offsetUnit: 'utf16' }
19
+ pageLimits?: { maxRequestChars: number, maxResponseChars: number, responseSerialization: 'JSON.stringify(value,null,2)' }
20
+ actionLimits?: { maxRequestChars: number, maxResponseChars: number, responseSerialization: 'JSON.stringify(value,null,2)' }
21
+ semanticActions?: readonly DocumentAuthoringAction['type'][]
22
+ components?: { parameters: true, stepParameters: true, restoreStepParameters: true, useExistingDefinition: true, impactRead: true, maxChanges: number, maxPathDepth: number, independentCopy: { available: false, reason: 'implementation-closure-unavailable' } }
23
+ operations: readonly DocumentEditOperation['type'][]
24
+ replacement: boolean
25
+ fileChanges: boolean
26
+ limits?: { maxFiles: number, maxTextBytes: number, maxOperations: number, planLifetimeMs: number, maxPlans: number, maxReceipts: number }
27
+ idempotency: 'process-current-source' | 'unavailable'
28
+ review: 'required-unsupported' | 'locks-and-history' | 'locks-only' | 'unavailable'
29
+ }
30
+
31
+ /** A pure local preflight. Files include lecture.json and their predicted post-save versions. */
32
+ export interface DocumentChangePlan {
33
+ apiVersion: typeof AUTHORING_API_VERSION
34
+ planId: string
35
+ changeId: string
36
+ document: string
37
+ source: CanonicalSource
38
+ digest: string
39
+ expiresAt: string
40
+ content: TeachingDocument
41
+ issues: DocumentIssue[]
42
+ files: { path: string, base: string | null, version: string }[]
43
+ impact: { sections: string[], blocks: string[], steps: string[], narrations: string[], assets: string[], definitions: string[], sectionOrder: boolean, production: boolean, comments: boolean, metadata: boolean }
44
+ }
45
+
46
+ export interface ApplyDocumentChange { change: DocumentChange, planId: string }
47
+ export interface DocumentPageDuplicationRequest { id: string, source: CanonicalSource, sectionId: string, title: string }
48
+ export type DocumentPageAction =
49
+ | { type: 'create', title: string, after?: string | null }
50
+ | { type: 'rename', sectionId: string, title: string }
51
+ | { type: 'move', sectionId: string, before: string | null }
52
+ | { type: 'delete', sectionId: string }
53
+ export interface DocumentPageIds { sectionId: string, blockId: string, nodeId: string, stepId: string }
54
+ export interface DocumentPageDeletionImpact {
55
+ sectionId: string
56
+ retainedEmpty: boolean
57
+ blocks: string[]
58
+ steps: string[]
59
+ narrations: string[]
60
+ retainedNarrations: string[]
61
+ cues: string[]
62
+ clips: string[]
63
+ detachedComments: string[]
64
+ references: ({ type: 'cue', narrationId: string, cueId: string, blockId: string, stepIds: string[] } | { type: 'inheritance', stepId: string, inheritedStepId: string })[]
65
+ }
66
+ export interface DocumentPageChangeRequest { id: string, source: CanonicalSource, action: DocumentPageAction }
67
+ export interface DocumentPageChangePlan extends DocumentChangePlan {
68
+ action: DocumentPageAction
69
+ sectionId: string
70
+ created?: DocumentPageIds
71
+ deletion?: DocumentPageDeletionImpact
72
+ change: DocumentChange
73
+ }
74
+ export interface DocumentPageChangeReference extends DocumentPageDuplicationReference { confirmDeletion?: true }
75
+ export interface DocumentPageChangeInspection { plan: DocumentPageChangePlan, currentSource: CanonicalSource, state: 'current' | 'applied' | 'old' }
76
+ export interface AppliedDocumentPageChange extends Omit<AppliedDocumentChange, 'content' | 'issues' | 'verification' | 'commit'> {
77
+ commit?: { id: string, origin: RevisionOrigin['kind'] }
78
+ planId: string
79
+ digest: string
80
+ action: DocumentPageAction['type']
81
+ sectionId: string
82
+ retainedEmpty: boolean
83
+ changed: boolean
84
+ impact: Record<keyof DocumentChangePlan['impact'], number | boolean>
85
+ verification: AppliedDocumentChange['verification'] & { plannedVersion: string, pageStateVerified: true }
86
+ }
87
+ export interface DocumentPageDuplicationPlan extends DocumentChangePlan {
88
+ action: 'duplicatePage'
89
+ section: { from: string, to: string }
90
+ /** Only copied declared identities; assets, definitions, opaque payloads and comments retain their references. */
91
+ mapping: Record<string, string>
92
+ /** Pass this exact candidate with planId to the existing apply operation. */
93
+ change: DocumentChange
94
+ }
95
+ export interface DocumentPageDuplicationReference { planId: string, source: CanonicalSource, digest: string }
96
+ export interface DocumentPageDuplicationRead { planId: string, digest: string }
97
+ export interface DocumentPageDuplicationInspection {
98
+ plan: DocumentPageDuplicationPlan
99
+ currentSource: CanonicalSource
100
+ state: 'current' | 'applied' | 'old'
101
+ }
102
+ export interface AppliedDocumentPageDuplication extends Omit<AppliedDocumentChange, 'content' | 'issues' | 'verification'> {
103
+ planId: string
104
+ digest: string
105
+ section: DocumentPageDuplicationPlan['section']
106
+ copied: { identities: number, sections: number, blocks: number, steps: number, narrations: number }
107
+ verification: AppliedDocumentChange['verification'] & { plannedVersion: string, pagePresent: true }
108
+ }
109
+ export const DOCUMENT_PAGE_DUPLICATION_SCHEMA = {
110
+ type: 'object', additionalProperties: false, required: ['id', 'source', 'sectionId', 'title'],
111
+ properties: {
112
+ id: { type: 'string', minLength: 1, maxLength: 200 },
113
+ source: { type: 'object', additionalProperties: false, required: ['documentId', 'version', 'fingerprint'], properties: {
114
+ documentId: { type: 'string', minLength: 1, maxLength: 200 }, version: { type: 'string', minLength: 1, maxLength: 128 }, fingerprint: { type: 'string', minLength: 1, maxLength: 128 },
115
+ } },
116
+ sectionId: { type: 'string', minLength: 1, maxLength: 200 }, title: { type: 'string', minLength: 1, maxLength: 1000 },
117
+ },
118
+ } as const
119
+ export const DOCUMENT_PAGE_DUPLICATION_APPLY_SCHEMA = {
120
+ type: 'object', additionalProperties: false, required: ['planId', 'source', 'digest'], properties: {
121
+ planId: { type: 'string', minLength: 1, maxLength: 200 },
122
+ source: DOCUMENT_PAGE_DUPLICATION_SCHEMA.properties.source,
123
+ digest: { type: 'string', pattern: '^[a-f0-9]{64}$' },
124
+ },
125
+ } as const
126
+ export const DOCUMENT_PAGE_CHANGE_SCHEMA = {
127
+ type: 'object', additionalProperties: false, required: ['id', 'source', 'action'], properties: {
128
+ id: DOCUMENT_PAGE_DUPLICATION_SCHEMA.properties.id,
129
+ source: DOCUMENT_PAGE_DUPLICATION_SCHEMA.properties.source,
130
+ action: { oneOf: [
131
+ { type: 'object', additionalProperties: false, required: ['type', 'title'], properties: { type: { const: 'create' }, title: DOCUMENT_PAGE_DUPLICATION_SCHEMA.properties.title, after: { type: ['string', 'null'], minLength: 1, maxLength: 200 } } },
132
+ { type: 'object', additionalProperties: false, required: ['type', 'sectionId', 'title'], properties: { type: { const: 'rename' }, sectionId: DOCUMENT_PAGE_DUPLICATION_SCHEMA.properties.sectionId, title: DOCUMENT_PAGE_DUPLICATION_SCHEMA.properties.title } },
133
+ { type: 'object', additionalProperties: false, required: ['type', 'sectionId', 'before'], properties: { type: { const: 'move' }, sectionId: DOCUMENT_PAGE_DUPLICATION_SCHEMA.properties.sectionId, before: { type: ['string', 'null'], minLength: 1, maxLength: 200 } } },
134
+ { type: 'object', additionalProperties: false, required: ['type', 'sectionId'], properties: { type: { const: 'delete' }, sectionId: DOCUMENT_PAGE_DUPLICATION_SCHEMA.properties.sectionId } },
135
+ ] },
136
+ },
137
+ } as const
138
+ export const DOCUMENT_PAGE_CHANGE_APPLY_SCHEMA = {
139
+ ...DOCUMENT_PAGE_DUPLICATION_APPLY_SCHEMA,
140
+ properties: { ...DOCUMENT_PAGE_DUPLICATION_APPLY_SCHEMA.properties, confirmDeletion: { const: true } },
141
+ } as const
142
+ export interface AppliedDocumentChange extends DocumentChangeAnswer {
143
+ verification: { saved: true, source: CanonicalSource, compiled: 'not-requested', runtime: 'not-requested' }
144
+ }
145
+
146
+ /**
147
+ * Injected by an authoring host. Neither MCP nor compilation owns file-writing semantics.
148
+ * Each `check*` runs its apply's checks (plan reference, review, current source or a replayable receipt, the dry-run transaction) without writing,
149
+ * so a doomed request is refused before the author is asked; the apply checks everything again.
150
+ */
151
+ export interface DocumentAuthoring {
152
+ capabilities(document: string, member: Member): AuthoringCapabilities
153
+ read(document: string, member: Member): Promise<DocumentAnswer>
154
+ outline(document: string, query: DocumentOutlineQuery, member: Member): Promise<DocumentOutlineAnswer>
155
+ readPart(document: string, query: DocumentPartQuery, member: Member): Promise<DocumentPartAnswer>
156
+ plan(document: string, change: DocumentChange, member: Member): Promise<DocumentChangePlan>
157
+ planPageDuplication(document: string, request: DocumentPageDuplicationRequest, member: Member): Promise<DocumentPageDuplicationPlan>
158
+ readPageDuplicationPlan(document: string, reference: DocumentPageDuplicationRead, member: Member): Promise<DocumentPageDuplicationInspection>
159
+ checkPageDuplication(document: string, reference: DocumentPageDuplicationReference, member: Member): Promise<void>
160
+ applyPageDuplication(document: string, reference: DocumentPageDuplicationReference, member: Member): Promise<AppliedDocumentPageDuplication>
161
+ planPageChange(document: string, request: DocumentPageChangeRequest, member: Member): Promise<DocumentPageChangePlan>
162
+ readPageChangePlan(document: string, reference: DocumentPageDuplicationRead, member: Member): Promise<DocumentPageChangeInspection>
163
+ checkPageChange(document: string, reference: DocumentPageChangeReference, member: Member): Promise<void>
164
+ applyPageChange(document: string, reference: DocumentPageChangeReference, member: Member): Promise<AppliedDocumentPageChange>
165
+ planAction(document: string, request: DocumentAuthoringActionRequest, member: Member): Promise<DocumentAuthoringActionPlan>
166
+ readActionPlan(document: string, reference: DocumentAuthoringActionRead, member: Member): Promise<DocumentAuthoringActionInspection>
167
+ readActionImpact(document: string, query: DocumentComponentImpactQuery, member: Member): Promise<DocumentComponentImpactAnswer>
168
+ checkAction(document: string, reference: DocumentAuthoringActionReference, member: Member): Promise<void>
169
+ applyAction(document: string, reference: DocumentAuthoringActionReference, member: Member): Promise<AppliedDocumentAuthoringAction>
170
+ checkApply(document: string, request: ApplyDocumentChange, member: Member): Promise<void>
171
+ apply(document: string, request: ApplyDocumentChange, member: Member): Promise<AppliedDocumentChange>
172
+ }
173
+
174
+ export interface AuthoringFailure {
175
+ code: string
176
+ message: string
177
+ status: number
178
+ retryable: boolean
179
+ detail?: Record<string, unknown>
180
+ }
@@ -0,0 +1,29 @@
1
+ import type { IRDocument } from '../ir.ts'
2
+ import type { AnchorLockFile, AnchorResolverRegistry, SourceReader } from './anchors.ts'
3
+ import type { ServiceToken } from './service.ts'
4
+
5
+ export type Severity = 'error' | 'warning' | 'info'
6
+
7
+ export interface Finding {
8
+ checkId: string
9
+ severity: Severity
10
+ message: string
11
+ source?: { file: string; line?: number }
12
+ hint?: string
13
+ }
14
+
15
+ export interface CheckContext {
16
+ deckDir: string
17
+ workspaceRoot: string
18
+ headmatter: Record<string, unknown>
19
+ src: SourceReader
20
+ resolvers: AnchorResolverRegistry
21
+ lock: AnchorLockFile
22
+ ir: IRDocument
23
+ service?<T>(token: ServiceToken<T>): T | undefined
24
+ }
25
+
26
+ export interface Check {
27
+ id: string
28
+ run(ctx: CheckContext): Promise<Finding[]>
29
+ }
@@ -0,0 +1,49 @@
1
+ import type { Unsubscribe } from './plugin.ts'
2
+ import { defineService } from './service.ts'
3
+ import type { Cursor } from './steps.ts'
4
+
5
+ export type ParticipantRole = 'presenter' | 'learner'
6
+
7
+ export interface Participant {
8
+ id: string
9
+ name: string
10
+ role: ParticipantRole
11
+ }
12
+
13
+ export type SessionStatus = 'idle' | 'active' | 'ended'
14
+
15
+ export interface SessionSnapshot {
16
+ sessionId?: string
17
+ status: SessionStatus
18
+ me?: Participant
19
+ participants: Participant[]
20
+ presenterCursor?: Cursor
21
+ }
22
+
23
+ export interface SessionService {
24
+ snapshot(): SessionSnapshot
25
+ subscribe(listener: (snapshot: SessionSnapshot) => void): Unsubscribe
26
+ create(name: string): Promise<string>
27
+ join(sessionId: string, name: string): Promise<void>
28
+ end(): Promise<void>
29
+ leave(): void
30
+ archive?(): Promise<string>
31
+ readonly personal?: ClassroomStore
32
+ }
33
+
34
+ export const SESSION = defineService<SessionService>('presmium.session')
35
+
36
+ export interface StoredItem<T> {
37
+ id: string
38
+ value: T
39
+ }
40
+
41
+ export interface ClassroomStore {
42
+ list<T>(collection: string): StoredItem<T>[]
43
+ get<T>(collection: string, id: string): T | undefined
44
+ put<T>(collection: string, id: string, value: T): void
45
+ remove(collection: string, id: string): void
46
+ subscribe(collection: string, listener: () => void): Unsubscribe
47
+ }
48
+
49
+ export const CLASSROOM_STORE = defineService<ClassroomStore>('presmium.classroomStore')
@@ -0,0 +1,37 @@
1
+ import type { Finding } from './checks.ts'
2
+ import type { CanonicalSource } from '../ir.ts'
3
+
4
+ export type DiagnosticOrigin = 'compile' | 'check' | 'preview' | 'runtime' | 'server' | 'authoring'
5
+
6
+ /** A current problem. Source paths are relative to the workspace root. */
7
+ export interface Diagnostic extends Finding {
8
+ id: string
9
+ document: string
10
+ origin: DiagnosticOrigin
11
+ renderer?: 'web' | 'slides'
12
+ revision?: string
13
+ /** Saved authoring baseline when this issue was observed, never a compiled IR revision. */
14
+ authoringSource?: CanonicalSource
15
+ }
16
+
17
+ /** Replaces the previous snapshot; it is not a history of diagnostic events. */
18
+ export interface DiagnosticSnapshot {
19
+ version: number
20
+ diagnostics: Diagnostic[]
21
+ }
22
+
23
+ /** The current problems reported by one embedded preview. */
24
+ export interface PreviewDiagnostics {
25
+ document: string
26
+ renderer: 'web' | 'slides'
27
+ instance: string
28
+ revision: string
29
+ findings: Finding[]
30
+ }
31
+
32
+ /** A selected author's window can report a failure before any runtime has compiled. */
33
+ export interface AuthoringDiagnostics {
34
+ document: string
35
+ instance: string
36
+ findings: { checkId: string, severity: 'error', message: string, authoringSource?: CanonicalSource }[]
37
+ }