@cat-factory/app 0.6.0 → 0.7.3

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 (33) hide show
  1. package/LICENSE +21 -21
  2. package/app/components/board/ContextPicker.vue +367 -367
  3. package/app/components/gates/GateResultView.vue +90 -12
  4. package/app/components/layout/SideBar.vue +11 -0
  5. package/app/components/observability/StepMetricsBar.vue +102 -102
  6. package/app/components/observability/StepModelActivity.vue +49 -0
  7. package/app/components/panels/ObservabilityPanel.vue +1 -1
  8. package/app/components/panels/StepMetadataCard.vue +4 -16
  9. package/app/components/panels/StepRunMeta.vue +105 -0
  10. package/app/components/panels/inspector/RecurringScheduleSettings.vue +178 -178
  11. package/app/components/panels/inspector/TaskRunSettings.vue +77 -0
  12. package/app/components/recurring/RecurrenceEditor.vue +124 -124
  13. package/app/components/settings/IssueTrackerWritebackPanel.vue +103 -0
  14. package/app/components/testing/TestReportWindow.vue +17 -8
  15. package/app/composables/useBlockQueries.ts +154 -154
  16. package/app/composables/useContextLinking.ts +65 -65
  17. package/app/composables/useFrameResize.ts +54 -54
  18. package/app/pages/index.vue +2 -0
  19. package/app/stores/documents.ts +176 -176
  20. package/app/stores/services.ts +87 -87
  21. package/app/stores/tracker.ts +39 -27
  22. package/app/stores/ui.ts +12 -0
  23. package/app/types/documents.ts +104 -104
  24. package/app/types/domain.ts +5 -1
  25. package/app/types/execution.ts +18 -0
  26. package/app/types/github.ts +173 -173
  27. package/app/types/services.ts +27 -27
  28. package/app/types/tasks.ts +82 -82
  29. package/app/types/tracker.ts +27 -18
  30. package/app/utils/agentOutput.spec.ts +128 -128
  31. package/app/utils/agentOutput.ts +173 -173
  32. package/app/utils/observability.ts +52 -52
  33. package/package.json +6 -1
@@ -1,104 +1,104 @@
1
- // ---------------------------------------------------------------------------
2
- // Document-source integration. Requirements / RFCs / PRDs imported from external
3
- // sources (Confluence, Notion, …) can be expanded into board structure or
4
- // attached to a task as agent context. These mirror the `@cat-factory/contracts`
5
- // document schemas; the abstraction is source-agnostic, keyed by `source`.
6
- // ---------------------------------------------------------------------------
7
-
8
- import type { BlockType } from './domain'
9
-
10
- /** The external document sources cat-factory can link to. */
11
- export type DocumentSourceKind = 'confluence' | 'notion' | 'github'
12
-
13
- /** One credential a provider needs to connect (rendered as a form field). */
14
- export interface CredentialField {
15
- key: string
16
- label: string
17
- help?: string
18
- placeholder?: string
19
- secret?: boolean
20
- }
21
-
22
- /** A source's self-description: drives the generic connect + import UI. */
23
- export interface DocumentSourceDescriptor {
24
- source: DocumentSourceKind
25
- label: string
26
- /** Lucide icon name for the source. */
27
- icon: string
28
- credentialFields: CredentialField[]
29
- refLabel: string
30
- refPlaceholder: string
31
- /** Whether the source supports searching its catalogue by title/content. */
32
- searchable?: boolean
33
- }
34
-
35
- /** A workspace's connection to a document source (never carries credentials). */
36
- export interface DocumentConnection {
37
- source: DocumentSourceKind
38
- /** Human-friendly label for what we're connected to (site URL, workspace name). */
39
- label: string
40
- /** When the connection was established (epoch ms). */
41
- connectedAt: number
42
- }
43
-
44
- /** A page imported from a source into the workspace. */
45
- export interface SourceDocument {
46
- source: DocumentSourceKind
47
- /** The source's stable id for the page. */
48
- externalId: string
49
- title: string
50
- url: string
51
- /** Short plain-text preview of the page body. */
52
- excerpt: string
53
- /** The board block this document is attached to as context, if any. */
54
- linkedBlockId: string | null
55
- syncedAt: number
56
- }
57
-
58
- /** A lean hit from searching a document source's catalogue (not yet imported). */
59
- export interface DocumentSearchResult {
60
- source: DocumentSourceKind
61
- /** The source's stable id for the page (re-usable as an import ref). */
62
- externalId: string
63
- title: string
64
- url: string
65
- /** Short plain-text preview (may be empty). */
66
- excerpt: string
67
- }
68
-
69
- /** A proposed task within a planned frame/module. */
70
- export interface PlanTask {
71
- title: string
72
- description?: string
73
- }
74
-
75
- /** A proposed module grouping tasks within a planned frame. */
76
- export interface PlanModule {
77
- name: string
78
- tasks: PlanTask[]
79
- }
80
-
81
- /** A proposed top-level frame with its modules and loose tasks. */
82
- export interface PlanFrame {
83
- type: BlockType
84
- title: string
85
- description?: string
86
- modules: PlanModule[]
87
- tasks: PlanTask[]
88
- }
89
-
90
- /** A board structure extracted from an imported document. */
91
- export interface DocumentBoardPlan {
92
- source: DocumentSourceKind
93
- externalId: string
94
- /** Whether an LLM produced the plan or the deterministic heading parser did. */
95
- planner: 'llm' | 'headings'
96
- frames: PlanFrame[]
97
- }
98
-
99
- /** Counts of blocks created by spawning a plan onto the board. */
100
- export interface SpawnResult {
101
- frames: number
102
- modules: number
103
- tasks: number
104
- }
1
+ // ---------------------------------------------------------------------------
2
+ // Document-source integration. Requirements / RFCs / PRDs imported from external
3
+ // sources (Confluence, Notion, …) can be expanded into board structure or
4
+ // attached to a task as agent context. These mirror the `@cat-factory/contracts`
5
+ // document schemas; the abstraction is source-agnostic, keyed by `source`.
6
+ // ---------------------------------------------------------------------------
7
+
8
+ import type { BlockType } from './domain'
9
+
10
+ /** The external document sources cat-factory can link to. */
11
+ export type DocumentSourceKind = 'confluence' | 'notion' | 'github'
12
+
13
+ /** One credential a provider needs to connect (rendered as a form field). */
14
+ export interface CredentialField {
15
+ key: string
16
+ label: string
17
+ help?: string
18
+ placeholder?: string
19
+ secret?: boolean
20
+ }
21
+
22
+ /** A source's self-description: drives the generic connect + import UI. */
23
+ export interface DocumentSourceDescriptor {
24
+ source: DocumentSourceKind
25
+ label: string
26
+ /** Lucide icon name for the source. */
27
+ icon: string
28
+ credentialFields: CredentialField[]
29
+ refLabel: string
30
+ refPlaceholder: string
31
+ /** Whether the source supports searching its catalogue by title/content. */
32
+ searchable?: boolean
33
+ }
34
+
35
+ /** A workspace's connection to a document source (never carries credentials). */
36
+ export interface DocumentConnection {
37
+ source: DocumentSourceKind
38
+ /** Human-friendly label for what we're connected to (site URL, workspace name). */
39
+ label: string
40
+ /** When the connection was established (epoch ms). */
41
+ connectedAt: number
42
+ }
43
+
44
+ /** A page imported from a source into the workspace. */
45
+ export interface SourceDocument {
46
+ source: DocumentSourceKind
47
+ /** The source's stable id for the page. */
48
+ externalId: string
49
+ title: string
50
+ url: string
51
+ /** Short plain-text preview of the page body. */
52
+ excerpt: string
53
+ /** The board block this document is attached to as context, if any. */
54
+ linkedBlockId: string | null
55
+ syncedAt: number
56
+ }
57
+
58
+ /** A lean hit from searching a document source's catalogue (not yet imported). */
59
+ export interface DocumentSearchResult {
60
+ source: DocumentSourceKind
61
+ /** The source's stable id for the page (re-usable as an import ref). */
62
+ externalId: string
63
+ title: string
64
+ url: string
65
+ /** Short plain-text preview (may be empty). */
66
+ excerpt: string
67
+ }
68
+
69
+ /** A proposed task within a planned frame/module. */
70
+ export interface PlanTask {
71
+ title: string
72
+ description?: string
73
+ }
74
+
75
+ /** A proposed module grouping tasks within a planned frame. */
76
+ export interface PlanModule {
77
+ name: string
78
+ tasks: PlanTask[]
79
+ }
80
+
81
+ /** A proposed top-level frame with its modules and loose tasks. */
82
+ export interface PlanFrame {
83
+ type: BlockType
84
+ title: string
85
+ description?: string
86
+ modules: PlanModule[]
87
+ tasks: PlanTask[]
88
+ }
89
+
90
+ /** A board structure extracted from an imported document. */
91
+ export interface DocumentBoardPlan {
92
+ source: DocumentSourceKind
93
+ externalId: string
94
+ /** Whether an LLM produced the plan or the deterministic heading parser did. */
95
+ planner: 'llm' | 'headings'
96
+ frames: PlanFrame[]
97
+ }
98
+
99
+ /** Counts of blocks created by spawning a plan onto the board. */
100
+ export interface SpawnResult {
101
+ frames: number
102
+ modules: number
103
+ tasks: number
104
+ }
@@ -23,7 +23,7 @@ import type { ClarityReview } from './clarity'
23
23
  import type { MergeThresholdPreset } from './merge'
24
24
  import type { PipelineSchedule } from './recurring'
25
25
  import type { Service, WorkspaceMount } from './services'
26
- import type { TrackerSettings } from './tracker'
26
+ import type { TrackerSettings, WritebackOverride } from './tracker'
27
27
 
28
28
  /** Lifecycle of an architecture building block. */
29
29
  export type BlockStatus =
@@ -145,6 +145,10 @@ export interface Block {
145
145
  * with the `product` role, notified when requirement review flags this task.
146
146
  */
147
147
  responsibleProductUserId?: string | null
148
+ /** task-only: override "comment on the linked issue when a PR opens"; absent/null = inherit workspace. */
149
+ trackerCommentOnPrOpen?: WritebackOverride | null
150
+ /** task-only: override "close the linked issue as resolved on merge"; absent/null = inherit workspace. */
151
+ trackerResolveOnMerge?: WritebackOverride | null
148
152
  }
149
153
 
150
154
  /**
@@ -333,6 +333,22 @@ export interface PipelineStep {
333
333
  export interface GateFailingCheck {
334
334
  name: string
335
335
  conclusion: string | null
336
+ /** GitHub web URL of the check run, so the UI can link to the failed run's logs */
337
+ url?: string | null
338
+ }
339
+
340
+ /** One helper-agent attempt the gate dispatched (mirrors `gateAttemptSchema`). */
341
+ export interface GateAttempt {
342
+ /** 1-based attempt number */
343
+ attempt: number
344
+ /** epoch ms when the helper job finished */
345
+ at: number
346
+ /** how the helper job ended */
347
+ outcome: 'completed' | 'failed'
348
+ /** the PR head commit the helper worked against, when known */
349
+ headSha?: string | null
350
+ /** the helper's own account of what it did / what remains */
351
+ summary?: string | null
336
352
  }
337
353
 
338
354
  /** Live state of a polling gate step (`ci` / `conflicts`); mirrors `gateStepStateSchema`. */
@@ -350,6 +366,8 @@ export interface GateStepState {
350
366
  lastFailureSummary?: string | null
351
367
  /** structured failing checks behind the summary (CI gate only; absent for conflicts) */
352
368
  failingChecks?: GateFailingCheck[] | null
369
+ /** history of the helper-agent attempts this gate dispatched, newest last */
370
+ attemptLog?: GateAttempt[] | null
353
371
  }
354
372
 
355
373
  /** Live state of a `tester` step's Tester→Fixer loop (mirrors `testerStepStateSchema`). */
@@ -1,173 +1,173 @@
1
- // ---------------------------------------------------------------------------
2
- // GitHub integration. The backend's `GitHubModule` projects GitHub data
3
- // (repos/branches, pull requests/issues) into D1 and serves it fast and
4
- // rate-limit-free, alongside connect/resync/write endpoints. These mirror the
5
- // `@cat-factory/contracts` GitHub schemas so responses drop straight into the
6
- // Pinia store, just as the document-source types do for Confluence/Notion.
7
- // ---------------------------------------------------------------------------
8
-
9
- /** A workspace's GitHub App installation, as exposed to clients (no token). */
10
- export interface GitHubConnection {
11
- installationId: number
12
- accountLogin: string
13
- targetType: 'Organization' | 'User'
14
- connectedAt: number
15
- /**
16
- * Whether cat-factory can create repos under this account itself (privileged
17
- * App tier). When true, the bootstrap UI drops the manual "create on GitHub"
18
- * step. Defaults to false for older backends that don't send it.
19
- */
20
- canCreateRepos?: boolean
21
- /**
22
- * Whether the installation granted the App `workflows: write`. When false, agent
23
- * pushes that add/update `.github/workflows/*` are rejected by GitHub, so the UI
24
- * warns the user to grant the permission. Defaults to false for older backends.
25
- */
26
- canManageWorkflows?: boolean
27
- }
28
-
29
- /**
30
- * A discoverable App installation for the connect picker. `connected` says
31
- * whether it's already bound: to THIS workspace, to ANOTHER (so connecting would
32
- * be rejected), or to NONE (free to connect).
33
- */
34
- export interface GitHubInstallationOption {
35
- installationId: number
36
- accountLogin: string
37
- targetType: 'Organization' | 'User'
38
- accountAvatarUrl: string | null
39
- connected: 'this' | 'other' | 'none'
40
- }
41
-
42
- /** A repository the integration tracks for a workspace. */
43
- export interface GitHubRepo {
44
- githubId: number
45
- installationId: number
46
- owner: string
47
- name: string
48
- defaultBranch: string | null
49
- private: boolean
50
- /** Optional link to a board block this repo backs. */
51
- blockId: string | null
52
- /** Whether this repo is a monorepo hosting several services (each pinned to a subdirectory). */
53
- isMonorepo?: boolean
54
- syncedAt: number
55
- }
56
-
57
- /** One directory entry of a repo's tree, used by the monorepo service picker. */
58
- export interface RepoTreeEntry {
59
- /** Path relative to the repo root, e.g. `packages/api`. */
60
- path: string
61
- /** Base name, e.g. `api`. */
62
- name: string
63
- /** `file` | `dir` | `symlink` | `submodule`. */
64
- type: string
65
- }
66
-
67
- export interface GitHubBranch {
68
- repoGithubId: number
69
- name: string
70
- headSha: string
71
- protected: boolean
72
- syncedAt: number
73
- }
74
-
75
- /**
76
- * A repo the connected installation can access, annotated with whether the
77
- * current workspace links it. Drives the per-workspace repo picker — repos are
78
- * linked explicitly per board, since the installation is shared across an
79
- * account's workspaces.
80
- */
81
- export interface GitHubAvailableRepo {
82
- githubId: number
83
- owner: string
84
- name: string
85
- defaultBranch: string | null
86
- private: boolean
87
- linked: boolean
88
- /** Whether the (linked) repo is flagged as a monorepo. */
89
- isMonorepo?: boolean
90
- }
91
-
92
- export type GitHubPullRequestState = 'open' | 'closed'
93
-
94
- export interface GitHubPullRequest {
95
- repoGithubId: number
96
- number: number
97
- githubId: number
98
- title: string
99
- state: GitHubPullRequestState
100
- headRef: string | null
101
- baseRef: string | null
102
- headSha: string | null
103
- merged: boolean
104
- author: string | null
105
- updatedAt: number | null
106
- syncedAt: number
107
- }
108
-
109
- export type GitHubIssueState = 'open' | 'closed'
110
-
111
- export interface GitHubIssue {
112
- repoGithubId: number
113
- number: number
114
- githubId: number
115
- title: string
116
- state: GitHubIssueState
117
- author: string | null
118
- labels: string[]
119
- updatedAt: number | null
120
- syncedAt: number
121
- }
122
-
123
- // ---- request inputs -------------------------------------------------------
124
-
125
- /** Trigger a resync. Defaults to an incremental resync of all tracked repos. */
126
- export interface ResyncRequest {
127
- /** Limit the resync to a single repo (by its GitHub numeric id). */
128
- repoGithubId?: number
129
- /** Run a full backfill (durable Workflow) instead of an incremental pass. */
130
- full?: boolean
131
- }
132
-
133
- export interface CreateBranchInput {
134
- name: string
135
- fromSha: string
136
- }
137
-
138
- /** Body for programmatic repo creation (privileged App tier). */
139
- export interface CreateRepoRequest {
140
- /** A single GitHub name segment — no "owner/" prefix. */
141
- name: string
142
- private?: boolean
143
- description?: string
144
- }
145
-
146
- /** The freshly-created repository returned by the create-repo endpoint. */
147
- export interface CreatedRepo {
148
- githubId: number
149
- owner: string
150
- name: string
151
- defaultBranch: string | null
152
- private: boolean
153
- }
154
-
155
- export interface CommitFilesInput {
156
- branch: string
157
- message: string
158
- files: { path: string; content: string }[]
159
- /** Parent commit to build on; defaults to the branch tip. */
160
- baseSha?: string
161
- }
162
-
163
- export interface OpenPullRequestInput {
164
- title: string
165
- head: string
166
- base: string
167
- body?: string
168
- draft?: boolean
169
- }
170
-
171
- export interface MergePullRequestInput {
172
- method?: 'merge' | 'squash' | 'rebase'
173
- }
1
+ // ---------------------------------------------------------------------------
2
+ // GitHub integration. The backend's `GitHubModule` projects GitHub data
3
+ // (repos/branches, pull requests/issues) into D1 and serves it fast and
4
+ // rate-limit-free, alongside connect/resync/write endpoints. These mirror the
5
+ // `@cat-factory/contracts` GitHub schemas so responses drop straight into the
6
+ // Pinia store, just as the document-source types do for Confluence/Notion.
7
+ // ---------------------------------------------------------------------------
8
+
9
+ /** A workspace's GitHub App installation, as exposed to clients (no token). */
10
+ export interface GitHubConnection {
11
+ installationId: number
12
+ accountLogin: string
13
+ targetType: 'Organization' | 'User'
14
+ connectedAt: number
15
+ /**
16
+ * Whether cat-factory can create repos under this account itself (privileged
17
+ * App tier). When true, the bootstrap UI drops the manual "create on GitHub"
18
+ * step. Defaults to false for older backends that don't send it.
19
+ */
20
+ canCreateRepos?: boolean
21
+ /**
22
+ * Whether the installation granted the App `workflows: write`. When false, agent
23
+ * pushes that add/update `.github/workflows/*` are rejected by GitHub, so the UI
24
+ * warns the user to grant the permission. Defaults to false for older backends.
25
+ */
26
+ canManageWorkflows?: boolean
27
+ }
28
+
29
+ /**
30
+ * A discoverable App installation for the connect picker. `connected` says
31
+ * whether it's already bound: to THIS workspace, to ANOTHER (so connecting would
32
+ * be rejected), or to NONE (free to connect).
33
+ */
34
+ export interface GitHubInstallationOption {
35
+ installationId: number
36
+ accountLogin: string
37
+ targetType: 'Organization' | 'User'
38
+ accountAvatarUrl: string | null
39
+ connected: 'this' | 'other' | 'none'
40
+ }
41
+
42
+ /** A repository the integration tracks for a workspace. */
43
+ export interface GitHubRepo {
44
+ githubId: number
45
+ installationId: number
46
+ owner: string
47
+ name: string
48
+ defaultBranch: string | null
49
+ private: boolean
50
+ /** Optional link to a board block this repo backs. */
51
+ blockId: string | null
52
+ /** Whether this repo is a monorepo hosting several services (each pinned to a subdirectory). */
53
+ isMonorepo?: boolean
54
+ syncedAt: number
55
+ }
56
+
57
+ /** One directory entry of a repo's tree, used by the monorepo service picker. */
58
+ export interface RepoTreeEntry {
59
+ /** Path relative to the repo root, e.g. `packages/api`. */
60
+ path: string
61
+ /** Base name, e.g. `api`. */
62
+ name: string
63
+ /** `file` | `dir` | `symlink` | `submodule`. */
64
+ type: string
65
+ }
66
+
67
+ export interface GitHubBranch {
68
+ repoGithubId: number
69
+ name: string
70
+ headSha: string
71
+ protected: boolean
72
+ syncedAt: number
73
+ }
74
+
75
+ /**
76
+ * A repo the connected installation can access, annotated with whether the
77
+ * current workspace links it. Drives the per-workspace repo picker — repos are
78
+ * linked explicitly per board, since the installation is shared across an
79
+ * account's workspaces.
80
+ */
81
+ export interface GitHubAvailableRepo {
82
+ githubId: number
83
+ owner: string
84
+ name: string
85
+ defaultBranch: string | null
86
+ private: boolean
87
+ linked: boolean
88
+ /** Whether the (linked) repo is flagged as a monorepo. */
89
+ isMonorepo?: boolean
90
+ }
91
+
92
+ export type GitHubPullRequestState = 'open' | 'closed'
93
+
94
+ export interface GitHubPullRequest {
95
+ repoGithubId: number
96
+ number: number
97
+ githubId: number
98
+ title: string
99
+ state: GitHubPullRequestState
100
+ headRef: string | null
101
+ baseRef: string | null
102
+ headSha: string | null
103
+ merged: boolean
104
+ author: string | null
105
+ updatedAt: number | null
106
+ syncedAt: number
107
+ }
108
+
109
+ export type GitHubIssueState = 'open' | 'closed'
110
+
111
+ export interface GitHubIssue {
112
+ repoGithubId: number
113
+ number: number
114
+ githubId: number
115
+ title: string
116
+ state: GitHubIssueState
117
+ author: string | null
118
+ labels: string[]
119
+ updatedAt: number | null
120
+ syncedAt: number
121
+ }
122
+
123
+ // ---- request inputs -------------------------------------------------------
124
+
125
+ /** Trigger a resync. Defaults to an incremental resync of all tracked repos. */
126
+ export interface ResyncRequest {
127
+ /** Limit the resync to a single repo (by its GitHub numeric id). */
128
+ repoGithubId?: number
129
+ /** Run a full backfill (durable Workflow) instead of an incremental pass. */
130
+ full?: boolean
131
+ }
132
+
133
+ export interface CreateBranchInput {
134
+ name: string
135
+ fromSha: string
136
+ }
137
+
138
+ /** Body for programmatic repo creation (privileged App tier). */
139
+ export interface CreateRepoRequest {
140
+ /** A single GitHub name segment — no "owner/" prefix. */
141
+ name: string
142
+ private?: boolean
143
+ description?: string
144
+ }
145
+
146
+ /** The freshly-created repository returned by the create-repo endpoint. */
147
+ export interface CreatedRepo {
148
+ githubId: number
149
+ owner: string
150
+ name: string
151
+ defaultBranch: string | null
152
+ private: boolean
153
+ }
154
+
155
+ export interface CommitFilesInput {
156
+ branch: string
157
+ message: string
158
+ files: { path: string; content: string }[]
159
+ /** Parent commit to build on; defaults to the branch tip. */
160
+ baseSha?: string
161
+ }
162
+
163
+ export interface OpenPullRequestInput {
164
+ title: string
165
+ head: string
166
+ base: string
167
+ body?: string
168
+ draft?: boolean
169
+ }
170
+
171
+ export interface MergePullRequestInput {
172
+ method?: 'merge' | 'squash' | 'rebase'
173
+ }
@@ -1,27 +1,27 @@
1
- // In-org shared services. Mirrors the `@cat-factory/contracts` `services` wire schemas:
2
- // a `Service` is the account-owned unit of work (a service frame + its subtree + repo),
3
- // shared across the workspaces that *mount* it; a `WorkspaceMount` places a service onto a
4
- // workspace board with that board's own frame layout override.
5
-
6
- export interface Service {
7
- id: string
8
- accountId: string | null
9
- frameBlockId: string
10
- installationId: number | null
11
- repoGithubId: number | null
12
- /** Subdirectory within the linked monorepo this service lives in (null = whole repo). */
13
- directory?: string | null
14
- createdAt: number
15
- /** How many boards mount this service. Set only on the org catalog (for the "Shared" badge). */
16
- mountCount?: number
17
- }
18
-
19
- export interface WorkspaceMount {
20
- workspaceId: string
21
- serviceId: string
22
- /** This board's frame position override. */
23
- position: { x: number; y: number }
24
- /** This board's dragged frame size; null/absent = auto-size. */
25
- size?: { w: number; h: number } | null
26
- createdAt: number
27
- }
1
+ // In-org shared services. Mirrors the `@cat-factory/contracts` `services` wire schemas:
2
+ // a `Service` is the account-owned unit of work (a service frame + its subtree + repo),
3
+ // shared across the workspaces that *mount* it; a `WorkspaceMount` places a service onto a
4
+ // workspace board with that board's own frame layout override.
5
+
6
+ export interface Service {
7
+ id: string
8
+ accountId: string | null
9
+ frameBlockId: string
10
+ installationId: number | null
11
+ repoGithubId: number | null
12
+ /** Subdirectory within the linked monorepo this service lives in (null = whole repo). */
13
+ directory?: string | null
14
+ createdAt: number
15
+ /** How many boards mount this service. Set only on the org catalog (for the "Shared" badge). */
16
+ mountCount?: number
17
+ }
18
+
19
+ export interface WorkspaceMount {
20
+ workspaceId: string
21
+ serviceId: string
22
+ /** This board's frame position override. */
23
+ position: { x: number; y: number }
24
+ /** This board's dragged frame size; null/absent = auto-size. */
25
+ size?: { w: number; h: number } | null
26
+ createdAt: number
27
+ }