@cat-factory/app 0.6.0 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,65 +1,65 @@
1
- import type { DocumentSourceKind, TaskSourceKind } from '~/types/domain'
2
-
3
- // Shared model + orchestration for attaching external context (imported docs and
4
- // tracker issues) to a board block. A "pending" item is something the user has
5
- // chosen to attach but which is only linked once the block exists — so the task
6
- // creation popup can collect selections up front and commit them after the task
7
- // is created. Search hits and pasted URLs carry `needsImport: true`: they are not
8
- // yet projected locally, so they are imported (fetched + persisted) before being
9
- // linked; already-imported items are linked directly. Reused wherever context is
10
- // attached (the add-task popup today; the inspector can adopt it later).
11
-
12
- export interface PendingContext {
13
- kind: 'document' | 'task'
14
- source: DocumentSourceKind | TaskSourceKind
15
- /** A canonical external id (already-imported / search hit) or a pasted URL/ref. */
16
- externalId: string
17
- title: string
18
- /** Secondary line: an issue status, a source label, the raw URL, … */
19
- subtitle?: string
20
- /** Lucide icon for the row. */
21
- icon?: string
22
- /** True when the item must be imported before it can be linked. */
23
- needsImport: boolean
24
- }
25
-
26
- /** Stable key for a pending item, used for dedupe + selection toggles. */
27
- export function contextKey(c: Pick<PendingContext, 'kind' | 'source' | 'externalId'>): string {
28
- return `${c.kind}:${c.source}:${c.externalId}`
29
- }
30
-
31
- export function useContextLinking() {
32
- const documents = useDocumentsStore()
33
- const tasks = useTasksStore()
34
-
35
- /**
36
- * Import (when needed) then link every pending item to `blockId`. Each failure
37
- * is counted rather than aborting the batch, so one bad attachment doesn't sink
38
- * the rest; returns how many failed.
39
- */
40
- async function linkPending(blockId: string, items: PendingContext[]): Promise<number> {
41
- let failed = 0
42
- for (const item of items) {
43
- try {
44
- if (item.kind === 'document') {
45
- const source = item.source as DocumentSourceKind
46
- const externalId = item.needsImport
47
- ? (await documents.importDocument(source, item.externalId)).externalId
48
- : item.externalId
49
- await documents.linkToBlock(blockId, source, externalId)
50
- } else {
51
- const source = item.source as TaskSourceKind
52
- const externalId = item.needsImport
53
- ? (await tasks.importTask(source, item.externalId)).externalId
54
- : item.externalId
55
- await tasks.linkToBlock(blockId, source, externalId)
56
- }
57
- } catch {
58
- failed++
59
- }
60
- }
61
- return failed
62
- }
63
-
64
- return { linkPending }
65
- }
1
+ import type { DocumentSourceKind, TaskSourceKind } from '~/types/domain'
2
+
3
+ // Shared model + orchestration for attaching external context (imported docs and
4
+ // tracker issues) to a board block. A "pending" item is something the user has
5
+ // chosen to attach but which is only linked once the block exists — so the task
6
+ // creation popup can collect selections up front and commit them after the task
7
+ // is created. Search hits and pasted URLs carry `needsImport: true`: they are not
8
+ // yet projected locally, so they are imported (fetched + persisted) before being
9
+ // linked; already-imported items are linked directly. Reused wherever context is
10
+ // attached (the add-task popup today; the inspector can adopt it later).
11
+
12
+ export interface PendingContext {
13
+ kind: 'document' | 'task'
14
+ source: DocumentSourceKind | TaskSourceKind
15
+ /** A canonical external id (already-imported / search hit) or a pasted URL/ref. */
16
+ externalId: string
17
+ title: string
18
+ /** Secondary line: an issue status, a source label, the raw URL, … */
19
+ subtitle?: string
20
+ /** Lucide icon for the row. */
21
+ icon?: string
22
+ /** True when the item must be imported before it can be linked. */
23
+ needsImport: boolean
24
+ }
25
+
26
+ /** Stable key for a pending item, used for dedupe + selection toggles. */
27
+ export function contextKey(c: Pick<PendingContext, 'kind' | 'source' | 'externalId'>): string {
28
+ return `${c.kind}:${c.source}:${c.externalId}`
29
+ }
30
+
31
+ export function useContextLinking() {
32
+ const documents = useDocumentsStore()
33
+ const tasks = useTasksStore()
34
+
35
+ /**
36
+ * Import (when needed) then link every pending item to `blockId`. Each failure
37
+ * is counted rather than aborting the batch, so one bad attachment doesn't sink
38
+ * the rest; returns how many failed.
39
+ */
40
+ async function linkPending(blockId: string, items: PendingContext[]): Promise<number> {
41
+ let failed = 0
42
+ for (const item of items) {
43
+ try {
44
+ if (item.kind === 'document') {
45
+ const source = item.source as DocumentSourceKind
46
+ const externalId = item.needsImport
47
+ ? (await documents.importDocument(source, item.externalId)).externalId
48
+ : item.externalId
49
+ await documents.linkToBlock(blockId, source, externalId)
50
+ } else {
51
+ const source = item.source as TaskSourceKind
52
+ const externalId = item.needsImport
53
+ ? (await tasks.importTask(source, item.externalId)).externalId
54
+ : item.externalId
55
+ await tasks.linkToBlock(blockId, source, externalId)
56
+ }
57
+ } catch {
58
+ failed++
59
+ }
60
+ }
61
+ return failed
62
+ }
63
+
64
+ return { linkPending }
65
+ }
@@ -1,54 +1,54 @@
1
- import { ref } from 'vue'
2
- import type { Block } from '~/types/domain'
3
-
4
- /**
5
- * Pointer-driven resizing for service frames (Miro-style border drag). The drag
6
- * delta is divided by the board zoom so the edge tracks the cursor, and the new
7
- * size is clamped to the frame's content extent so dragging in never clips the
8
- * tasks/modules inside. The frame grows live (the store block is mutated in place,
9
- * which `containerSize` reads back), and the final size is persisted once on
10
- * release rather than on every move.
11
- */
12
- export function useFrameResize() {
13
- const board = useBoardStore()
14
- const ui = useUiStore()
15
- /** Id of the frame currently being resized, for cursor/handle styling. */
16
- const resizingId = ref<string | null>(null)
17
-
18
- /**
19
- * Begin a resize from one of the frame's edges/corner. `edge` selects which
20
- * dimensions move: `'e'` width only, `'s'` height only, `'se'` both.
21
- */
22
- function startResize(block: Block, e: PointerEvent, edge: 'e' | 's' | 'se') {
23
- if (e.button !== 0) return
24
- e.preventDefault()
25
- e.stopPropagation()
26
- const startX = e.clientX
27
- const startY = e.clientY
28
- // The content extent is the floor — never shrink a frame below its tasks.
29
- const min = board.contentSize(block.id)
30
- // Seed from the current rendered size so the first move doesn't jump.
31
- const start = board.containerSize(block.id)
32
- resizingId.value = block.id
33
-
34
- const onMove = (ev: PointerEvent) => {
35
- const z = ui.zoom || 1
36
- const w = edge === 's' ? start.w : Math.max(min.w, start.w + (ev.clientX - startX) / z)
37
- const h = edge === 'e' ? start.h : Math.max(min.h, start.h + (ev.clientY - startY) / z)
38
- // Optimistic, local-only: mutate the cached block so the frame grows live
39
- // without a round-trip on every pointer move.
40
- block.size = { w: Math.round(w), h: Math.round(h) }
41
- }
42
- const onUp = () => {
43
- window.removeEventListener('pointermove', onMove)
44
- window.removeEventListener('pointerup', onUp)
45
- resizingId.value = null
46
- // Persist the final size once (also re-applies it as the authoritative block).
47
- if (block.size) void board.updateBlock(block.id, { size: block.size })
48
- }
49
- window.addEventListener('pointermove', onMove)
50
- window.addEventListener('pointerup', onUp)
51
- }
52
-
53
- return { resizingId, startResize }
54
- }
1
+ import { ref } from 'vue'
2
+ import type { Block } from '~/types/domain'
3
+
4
+ /**
5
+ * Pointer-driven resizing for service frames (Miro-style border drag). The drag
6
+ * delta is divided by the board zoom so the edge tracks the cursor, and the new
7
+ * size is clamped to the frame's content extent so dragging in never clips the
8
+ * tasks/modules inside. The frame grows live (the store block is mutated in place,
9
+ * which `containerSize` reads back), and the final size is persisted once on
10
+ * release rather than on every move.
11
+ */
12
+ export function useFrameResize() {
13
+ const board = useBoardStore()
14
+ const ui = useUiStore()
15
+ /** Id of the frame currently being resized, for cursor/handle styling. */
16
+ const resizingId = ref<string | null>(null)
17
+
18
+ /**
19
+ * Begin a resize from one of the frame's edges/corner. `edge` selects which
20
+ * dimensions move: `'e'` width only, `'s'` height only, `'se'` both.
21
+ */
22
+ function startResize(block: Block, e: PointerEvent, edge: 'e' | 's' | 'se') {
23
+ if (e.button !== 0) return
24
+ e.preventDefault()
25
+ e.stopPropagation()
26
+ const startX = e.clientX
27
+ const startY = e.clientY
28
+ // The content extent is the floor — never shrink a frame below its tasks.
29
+ const min = board.contentSize(block.id)
30
+ // Seed from the current rendered size so the first move doesn't jump.
31
+ const start = board.containerSize(block.id)
32
+ resizingId.value = block.id
33
+
34
+ const onMove = (ev: PointerEvent) => {
35
+ const z = ui.zoom || 1
36
+ const w = edge === 's' ? start.w : Math.max(min.w, start.w + (ev.clientX - startX) / z)
37
+ const h = edge === 'e' ? start.h : Math.max(min.h, start.h + (ev.clientY - startY) / z)
38
+ // Optimistic, local-only: mutate the cached block so the frame grows live
39
+ // without a round-trip on every pointer move.
40
+ block.size = { w: Math.round(w), h: Math.round(h) }
41
+ }
42
+ const onUp = () => {
43
+ window.removeEventListener('pointermove', onMove)
44
+ window.removeEventListener('pointerup', onUp)
45
+ resizingId.value = null
46
+ // Persist the final size once (also re-applies it as the authoritative block).
47
+ if (block.size) void board.updateBlock(block.id, { size: block.size })
48
+ }
49
+ window.addEventListener('pointermove', onMove)
50
+ window.addEventListener('pointerup', onUp)
51
+ }
52
+
53
+ return { resizingId, startResize }
54
+ }
@@ -26,6 +26,7 @@ import GitHubOnboarding from '~/components/github/GitHubOnboarding.vue'
26
26
  import FragmentLibraryPanel from '~/components/fragments/FragmentLibraryPanel.vue'
27
27
  import CommandBar from '~/components/layout/CommandBar.vue'
28
28
  import MergeThresholdsPanel from '~/components/settings/MergeThresholdsPanel.vue'
29
+ import IssueTrackerWritebackPanel from '~/components/settings/IssueTrackerWritebackPanel.vue'
29
30
  import WorkspaceSettingsPanel from '~/components/settings/WorkspaceSettingsPanel.vue'
30
31
  import DatadogPanel from '~/components/settings/DatadogPanel.vue'
31
32
  import ModelDefaultsPanel from '~/components/settings/ModelDefaultsPanel.vue'
@@ -115,6 +116,7 @@ watch(
115
116
  <FragmentLibraryPanel />
116
117
  <CommandBar />
117
118
  <MergeThresholdsPanel />
119
+ <IssueTrackerWritebackPanel />
118
120
  <WorkspaceSettingsPanel />
119
121
  <DatadogPanel />
120
122
  <ModelDefaultsPanel />
@@ -1,176 +1,176 @@
1
- import { defineStore } from 'pinia'
2
- import { computed, ref } from 'vue'
3
- import type {
4
- DocumentBoardPlan,
5
- DocumentConnection,
6
- DocumentSearchResult,
7
- DocumentSourceDescriptor,
8
- DocumentSourceKind,
9
- SourceDocument,
10
- } from '~/types/domain'
11
- import { useWorkspaceStore } from '~/stores/workspace'
12
-
13
- /**
14
- * Document-source integration state: the sources the backend offers (and their
15
- * connect metadata), the workspace's per-source connections, and the pages it
16
- * has imported — plus the actions that connect/import/plan/spawn/link against the
17
- * backend. `available` mirrors the backend's opt-in gate: a 503 from the source
18
- * probe means the integration is off, and the UI hides its entry points (just as
19
- * `auth.required` gates the login UI). The abstraction is source-agnostic; every
20
- * action is keyed by a `DocumentSourceKind`. Per-workspace, like the board
21
- * itself; nothing is persisted client-side.
22
- */
23
- export const useDocumentsStore = defineStore('documents', () => {
24
- const api = useApi()
25
- const workspace = useWorkspaceStore()
26
-
27
- /** null = unknown (not probed yet), true/false = integration on/off. */
28
- const available = ref<boolean | null>(null)
29
- /** The configured sources and their connect/import descriptors. */
30
- const sources = ref<DocumentSourceDescriptor[]>([])
31
- /** Live connections, one per connected source. */
32
- const connections = ref<DocumentConnection[]>([])
33
- const documents = ref<SourceDocument[]>([])
34
- const loading = ref(false)
35
-
36
- /** Sources the workspace currently has a live connection to. */
37
- const connectedSources = computed(() =>
38
- sources.value.filter((s) => connections.value.some((c) => c.source === s.source)),
39
- )
40
- const anyConnected = computed(() => connections.value.length > 0)
41
-
42
- function descriptorFor(source: DocumentSourceKind): DocumentSourceDescriptor | undefined {
43
- return sources.value.find((s) => s.source === source)
44
- }
45
-
46
- function connectionFor(source: DocumentSourceKind): DocumentConnection | undefined {
47
- return connections.value.find((c) => c.source === source)
48
- }
49
-
50
- function isConnected(source: DocumentSourceKind): boolean {
51
- return connectionFor(source) !== undefined
52
- }
53
-
54
- /** Imported documents currently attached to a given block. */
55
- function docsForBlock(blockId: string): SourceDocument[] {
56
- return documents.value.filter((d) => d.linkedBlockId === blockId)
57
- }
58
-
59
- /** Merge a document returned by the backend into the local cache. */
60
- function upsertDoc(doc: SourceDocument) {
61
- const i = documents.value.findIndex(
62
- (d) => d.source === doc.source && d.externalId === doc.externalId,
63
- )
64
- if (i >= 0) documents.value[i] = doc
65
- else documents.value.unshift(doc)
66
- }
67
-
68
- function upsertConnection(conn: DocumentConnection) {
69
- const i = connections.value.findIndex((c) => c.source === conn.source)
70
- if (i >= 0) connections.value[i] = conn
71
- else connections.value.push(conn)
72
- }
73
-
74
- /** Probe the integration: resolves `available`, the sources and connections. */
75
- async function probe() {
76
- if (!workspace.workspaceId) return
77
- try {
78
- const [{ sources: srcs }, { connections: conns }] = await Promise.all([
79
- api.listDocumentSources(workspace.requireId()),
80
- api.listDocumentConnections(workspace.requireId()),
81
- ])
82
- available.value = true
83
- sources.value = srcs
84
- connections.value = conns
85
- } catch {
86
- // 503 (integration disabled) or any error → hide the UI entry points.
87
- available.value = false
88
- sources.value = []
89
- connections.value = []
90
- }
91
- }
92
-
93
- /** Connect the workspace to a source with its credential bag. */
94
- async function connect(source: DocumentSourceKind, credentials: Record<string, string>) {
95
- const conn = await api.connectDocumentSource(workspace.requireId(), source, credentials)
96
- upsertConnection(conn)
97
- available.value = true
98
- }
99
-
100
- /** Disconnect the workspace from a source. */
101
- async function disconnect(source: DocumentSourceKind) {
102
- await api.disconnectDocumentSource(workspace.requireId(), source)
103
- connections.value = connections.value.filter((c) => c.source !== source)
104
- }
105
-
106
- /** Load the imported documents for the workspace (across sources). */
107
- async function loadDocuments() {
108
- documents.value = await api.listDocuments(workspace.requireId())
109
- }
110
-
111
- /** Import (fetch + persist) a page by id or URL from a source. */
112
- async function importDocument(source: DocumentSourceKind, ref: string): Promise<SourceDocument> {
113
- loading.value = true
114
- try {
115
- const doc = await api.importDocument(workspace.requireId(), source, { ref })
116
- upsertDoc(doc)
117
- return doc
118
- } finally {
119
- loading.value = false
120
- }
121
- }
122
-
123
- /** Search a connected source's catalogue by free text (title/content). */
124
- async function search(
125
- source: DocumentSourceKind,
126
- query: string,
127
- ): Promise<DocumentSearchResult[]> {
128
- const { results } = await api.searchDocumentSource(workspace.requireId(), source, query)
129
- return results
130
- }
131
-
132
- /** Preview the board structure a page would expand into (no writes). */
133
- function plan(source: DocumentSourceKind, externalId: string): Promise<DocumentBoardPlan> {
134
- return api.planDocument(workspace.requireId(), source, externalId)
135
- }
136
-
137
- /** Apply a page's structure to the board, then refresh the board snapshot. */
138
- async function spawn(source: DocumentSourceKind, externalId: string, frameId?: string) {
139
- const { result } = await api.spawnDocument(workspace.requireId(), source, {
140
- externalId,
141
- frameId,
142
- })
143
- await workspace.refresh()
144
- return result
145
- }
146
-
147
- /** Attach an imported page to a block as agent context. */
148
- async function linkToBlock(blockId: string, source: DocumentSourceKind, externalId: string) {
149
- const doc = await api.linkDocument(workspace.requireId(), { source, externalId, blockId })
150
- upsertDoc(doc)
151
- return doc
152
- }
153
-
154
- return {
155
- available,
156
- sources,
157
- connections,
158
- documents,
159
- loading,
160
- connectedSources,
161
- anyConnected,
162
- descriptorFor,
163
- connectionFor,
164
- isConnected,
165
- docsForBlock,
166
- probe,
167
- connect,
168
- disconnect,
169
- loadDocuments,
170
- importDocument,
171
- search,
172
- plan,
173
- spawn,
174
- linkToBlock,
175
- }
176
- })
1
+ import { defineStore } from 'pinia'
2
+ import { computed, ref } from 'vue'
3
+ import type {
4
+ DocumentBoardPlan,
5
+ DocumentConnection,
6
+ DocumentSearchResult,
7
+ DocumentSourceDescriptor,
8
+ DocumentSourceKind,
9
+ SourceDocument,
10
+ } from '~/types/domain'
11
+ import { useWorkspaceStore } from '~/stores/workspace'
12
+
13
+ /**
14
+ * Document-source integration state: the sources the backend offers (and their
15
+ * connect metadata), the workspace's per-source connections, and the pages it
16
+ * has imported — plus the actions that connect/import/plan/spawn/link against the
17
+ * backend. `available` mirrors the backend's opt-in gate: a 503 from the source
18
+ * probe means the integration is off, and the UI hides its entry points (just as
19
+ * `auth.required` gates the login UI). The abstraction is source-agnostic; every
20
+ * action is keyed by a `DocumentSourceKind`. Per-workspace, like the board
21
+ * itself; nothing is persisted client-side.
22
+ */
23
+ export const useDocumentsStore = defineStore('documents', () => {
24
+ const api = useApi()
25
+ const workspace = useWorkspaceStore()
26
+
27
+ /** null = unknown (not probed yet), true/false = integration on/off. */
28
+ const available = ref<boolean | null>(null)
29
+ /** The configured sources and their connect/import descriptors. */
30
+ const sources = ref<DocumentSourceDescriptor[]>([])
31
+ /** Live connections, one per connected source. */
32
+ const connections = ref<DocumentConnection[]>([])
33
+ const documents = ref<SourceDocument[]>([])
34
+ const loading = ref(false)
35
+
36
+ /** Sources the workspace currently has a live connection to. */
37
+ const connectedSources = computed(() =>
38
+ sources.value.filter((s) => connections.value.some((c) => c.source === s.source)),
39
+ )
40
+ const anyConnected = computed(() => connections.value.length > 0)
41
+
42
+ function descriptorFor(source: DocumentSourceKind): DocumentSourceDescriptor | undefined {
43
+ return sources.value.find((s) => s.source === source)
44
+ }
45
+
46
+ function connectionFor(source: DocumentSourceKind): DocumentConnection | undefined {
47
+ return connections.value.find((c) => c.source === source)
48
+ }
49
+
50
+ function isConnected(source: DocumentSourceKind): boolean {
51
+ return connectionFor(source) !== undefined
52
+ }
53
+
54
+ /** Imported documents currently attached to a given block. */
55
+ function docsForBlock(blockId: string): SourceDocument[] {
56
+ return documents.value.filter((d) => d.linkedBlockId === blockId)
57
+ }
58
+
59
+ /** Merge a document returned by the backend into the local cache. */
60
+ function upsertDoc(doc: SourceDocument) {
61
+ const i = documents.value.findIndex(
62
+ (d) => d.source === doc.source && d.externalId === doc.externalId,
63
+ )
64
+ if (i >= 0) documents.value[i] = doc
65
+ else documents.value.unshift(doc)
66
+ }
67
+
68
+ function upsertConnection(conn: DocumentConnection) {
69
+ const i = connections.value.findIndex((c) => c.source === conn.source)
70
+ if (i >= 0) connections.value[i] = conn
71
+ else connections.value.push(conn)
72
+ }
73
+
74
+ /** Probe the integration: resolves `available`, the sources and connections. */
75
+ async function probe() {
76
+ if (!workspace.workspaceId) return
77
+ try {
78
+ const [{ sources: srcs }, { connections: conns }] = await Promise.all([
79
+ api.listDocumentSources(workspace.requireId()),
80
+ api.listDocumentConnections(workspace.requireId()),
81
+ ])
82
+ available.value = true
83
+ sources.value = srcs
84
+ connections.value = conns
85
+ } catch {
86
+ // 503 (integration disabled) or any error → hide the UI entry points.
87
+ available.value = false
88
+ sources.value = []
89
+ connections.value = []
90
+ }
91
+ }
92
+
93
+ /** Connect the workspace to a source with its credential bag. */
94
+ async function connect(source: DocumentSourceKind, credentials: Record<string, string>) {
95
+ const conn = await api.connectDocumentSource(workspace.requireId(), source, credentials)
96
+ upsertConnection(conn)
97
+ available.value = true
98
+ }
99
+
100
+ /** Disconnect the workspace from a source. */
101
+ async function disconnect(source: DocumentSourceKind) {
102
+ await api.disconnectDocumentSource(workspace.requireId(), source)
103
+ connections.value = connections.value.filter((c) => c.source !== source)
104
+ }
105
+
106
+ /** Load the imported documents for the workspace (across sources). */
107
+ async function loadDocuments() {
108
+ documents.value = await api.listDocuments(workspace.requireId())
109
+ }
110
+
111
+ /** Import (fetch + persist) a page by id or URL from a source. */
112
+ async function importDocument(source: DocumentSourceKind, ref: string): Promise<SourceDocument> {
113
+ loading.value = true
114
+ try {
115
+ const doc = await api.importDocument(workspace.requireId(), source, { ref })
116
+ upsertDoc(doc)
117
+ return doc
118
+ } finally {
119
+ loading.value = false
120
+ }
121
+ }
122
+
123
+ /** Search a connected source's catalogue by free text (title/content). */
124
+ async function search(
125
+ source: DocumentSourceKind,
126
+ query: string,
127
+ ): Promise<DocumentSearchResult[]> {
128
+ const { results } = await api.searchDocumentSource(workspace.requireId(), source, query)
129
+ return results
130
+ }
131
+
132
+ /** Preview the board structure a page would expand into (no writes). */
133
+ function plan(source: DocumentSourceKind, externalId: string): Promise<DocumentBoardPlan> {
134
+ return api.planDocument(workspace.requireId(), source, externalId)
135
+ }
136
+
137
+ /** Apply a page's structure to the board, then refresh the board snapshot. */
138
+ async function spawn(source: DocumentSourceKind, externalId: string, frameId?: string) {
139
+ const { result } = await api.spawnDocument(workspace.requireId(), source, {
140
+ externalId,
141
+ frameId,
142
+ })
143
+ await workspace.refresh()
144
+ return result
145
+ }
146
+
147
+ /** Attach an imported page to a block as agent context. */
148
+ async function linkToBlock(blockId: string, source: DocumentSourceKind, externalId: string) {
149
+ const doc = await api.linkDocument(workspace.requireId(), { source, externalId, blockId })
150
+ upsertDoc(doc)
151
+ return doc
152
+ }
153
+
154
+ return {
155
+ available,
156
+ sources,
157
+ connections,
158
+ documents,
159
+ loading,
160
+ connectedSources,
161
+ anyConnected,
162
+ descriptorFor,
163
+ connectionFor,
164
+ isConnected,
165
+ docsForBlock,
166
+ probe,
167
+ connect,
168
+ disconnect,
169
+ loadDocuments,
170
+ importDocument,
171
+ search,
172
+ plan,
173
+ spawn,
174
+ linkToBlock,
175
+ }
176
+ })