@cat-factory/app 0.37.2 → 0.38.0

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 (39) hide show
  1. package/app/components/auth/AuthGate.vue +8 -0
  2. package/app/components/auth/LoginScreen.vue +86 -8
  3. package/app/components/auth/ResetPasswordScreen.vue +106 -0
  4. package/app/components/board/nodes/BlockNode.vue +32 -13
  5. package/app/components/bootstrap/BootstrapModal.vue +10 -6
  6. package/app/components/documents/DocumentImportModal.vue +11 -7
  7. package/app/components/github/AddServiceFromRepoModal.vue +9 -5
  8. package/app/components/github/GitHubPanel.vue +8 -4
  9. package/app/components/kaizen/KaizenPanel.vue +7 -3
  10. package/app/components/layout/IntegrationsHub.vue +2 -0
  11. package/app/components/panels/ObservabilityPanel.vue +12 -7
  12. package/app/components/providers/VendorCredentialsModal.vue +10 -6
  13. package/app/components/sandbox/SandboxPanel.vue +30 -19
  14. package/app/components/settings/IssueTrackerPanel.vue +3 -1
  15. package/app/components/settings/LocalModeSettingsPanel.vue +7 -3
  16. package/app/components/settings/LocalModelEndpointsPanel.vue +7 -3
  17. package/app/components/settings/ModelConfigurationPanel.vue +12 -8
  18. package/app/components/settings/ObservabilityConnectionPanel.vue +16 -12
  19. package/app/components/settings/OpenRouterCatalogPanel.vue +14 -9
  20. package/app/components/settings/ProviderConnectionPanel.vue +4 -4
  21. package/app/components/settings/UserSecretsSection.vue +7 -3
  22. package/app/components/settings/WorkspaceSettingsPanel.vue +3 -1
  23. package/app/components/slack/SlackPanel.vue +2 -0
  24. package/app/composables/api/auth.ts +11 -0
  25. package/app/composables/useBlockQueries.ts +31 -9
  26. package/app/pages/index.vue +103 -51
  27. package/app/pages/reset-password.vue +7 -0
  28. package/app/stores/auth.ts +12 -0
  29. package/app/stores/board.spec.ts +30 -0
  30. package/app/stores/board.ts +27 -2
  31. package/app/stores/brainstorm.ts +11 -0
  32. package/app/stores/clarity.ts +11 -0
  33. package/app/stores/consensus.ts +7 -1
  34. package/app/stores/execution.spec.ts +43 -0
  35. package/app/stores/execution.ts +19 -0
  36. package/app/stores/github.ts +17 -0
  37. package/app/stores/requirements.ts +12 -0
  38. package/app/stores/workspace.ts +17 -0
  39. package/package.json +2 -2
@@ -0,0 +1,43 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest'
2
+ import { useExecutionStore } from '~/stores/execution'
3
+ import type { ExecutionInstance } from '~/types/domain'
4
+
5
+ /**
6
+ * Minimal instance shape — the `decisionsByBlock` / `approvalsByBlock` getters only read
7
+ * `id`, `blockId` and each step's `{ decision, approval, agentKind }`, so a cast keeps the
8
+ * fixtures focused on the grouping behaviour rather than the full wire contract.
9
+ */
10
+ function instance(id: string, blockId: string, steps: unknown[]): ExecutionInstance {
11
+ return { id, blockId, steps } as unknown as ExecutionInstance
12
+ }
13
+
14
+ describe('execution store gate grouping', () => {
15
+ let store: ReturnType<typeof useExecutionStore>
16
+ beforeEach(() => {
17
+ store = useExecutionStore()
18
+ })
19
+
20
+ it('decisionsByBlock groups open (unchosen) decisions by block', () => {
21
+ store.hydrate([
22
+ instance('e1', 'b1', [
23
+ { agentKind: 'coder', decision: { id: 'd1', chosen: null } },
24
+ { agentKind: 'coder', decision: { id: 'd2', chosen: 'yes' } }, // chosen ⇒ excluded
25
+ ]),
26
+ instance('e2', 'b2', [{ agentKind: 'architect', decision: { id: 'd3', chosen: null } }]),
27
+ ])
28
+ expect(store.decisionsByBlock.get('b1')?.map((d) => d.decision.id)).toEqual(['d1'])
29
+ expect(store.decisionsByBlock.get('b2')?.map((d) => d.decision.id)).toEqual(['d3'])
30
+ expect(store.decisionsByBlock.has('missing')).toBe(false)
31
+ })
32
+
33
+ it('approvalsByBlock groups pending approvals by block', () => {
34
+ store.hydrate([
35
+ instance('e1', 'b1', [
36
+ { agentKind: 'merger', approval: { id: 'a1', status: 'pending' } },
37
+ { agentKind: 'merger', approval: { id: 'a2', status: 'approved' } }, // not pending ⇒ excluded
38
+ ]),
39
+ ])
40
+ expect(store.approvalsByBlock.get('b1')?.map((a) => a.approval.id)).toEqual(['a1'])
41
+ expect(store.approvalsByBlock.get('b2')).toBeUndefined()
42
+ })
43
+ })
@@ -106,6 +106,23 @@ export const useExecutionStore = defineStore('execution', () => {
106
106
  return out
107
107
  })
108
108
 
109
+ /**
110
+ * Open decisions/approvals grouped by the block they belong to, so a board card
111
+ * resolves its own + its tasks' pending gates with O(1) lookups instead of
112
+ * re-filtering the global lists once per frame on every execution event.
113
+ */
114
+ function groupByBlock<T extends { blockId: string }>(items: T[]): Map<string, T[]> {
115
+ const map = new Map<string, T[]>()
116
+ for (const item of items) {
117
+ const list = map.get(item.blockId)
118
+ if (list) list.push(item)
119
+ else map.set(item.blockId, [item])
120
+ }
121
+ return map
122
+ }
123
+ const decisionsByBlock = computed(() => groupByBlock(openDecisions.value))
124
+ const approvalsByBlock = computed(() => groupByBlock(openApprovals.value))
125
+
109
126
  /**
110
127
  * Start `pipeline` against a block; the server marks the block in-progress. A block
111
128
  * pinned to an individual-usage model (Claude) needs the initiator's personal
@@ -279,6 +296,8 @@ export const useExecutionStore = defineStore('execution', () => {
279
296
  pendingDecisionCount,
280
297
  openDecisions,
281
298
  openApprovals,
299
+ decisionsByBlock,
300
+ approvalsByBlock,
282
301
  pendingApprovalCount,
283
302
  start,
284
303
  resolveDecision,
@@ -259,6 +259,22 @@ export const useGitHubStore = defineStore('github', () => {
259
259
  return api.commentGitHubIssue(workspace.requireId(), repoGithubId, number, body)
260
260
  }
261
261
 
262
+ /**
263
+ * Drop the per-workspace projection + connection state (called on workspace switch)
264
+ * so the prior workspace's repos/PRs/issues don't linger until the re-probe + reload
265
+ * land. `available` returns to `null` (unknown) so the onboarding gate re-resolves.
266
+ */
267
+ function reset() {
268
+ available.value = null
269
+ connection.value = null
270
+ installations.value = []
271
+ repos.value = []
272
+ availableRepos.value = []
273
+ pulls.value = []
274
+ issues.value = []
275
+ branches.value = {}
276
+ }
277
+
262
278
  return {
263
279
  available,
264
280
  connection,
@@ -300,5 +316,6 @@ export const useGitHubStore = defineStore('github', () => {
300
316
  openPullRequest,
301
317
  mergePullRequest,
302
318
  comment,
319
+ reset,
303
320
  }
304
321
  })
@@ -97,6 +97,17 @@ export const useRequirementsStore = defineStore('requirements', () => {
97
97
  reviews.value = { ...reviews.value, [review.blockId]: review }
98
98
  }
99
99
 
100
+ /** Drop all cached reviews + in-flight state (called on workspace switch). */
101
+ function reset() {
102
+ available.value = null
103
+ reviews.value = {}
104
+ reviewing.value = new Set()
105
+ incorporating.value = new Set()
106
+ recommending.value = new Set()
107
+ loadingByBlock.value = new Set()
108
+ inFlight.clear()
109
+ }
110
+
100
111
  function withFlag(set: typeof reviewing, key: string, on: boolean) {
101
112
  const next = new Set(set.value)
102
113
  if (on) next.add(key)
@@ -265,6 +276,7 @@ export const useRequirementsStore = defineStore('requirements', () => {
265
276
  acceptRecommendation,
266
277
  rejectRecommendation,
267
278
  reRequestRecommendation,
279
+ reset,
268
280
  // Patch the cache from a live `requirements` stream event.
269
281
  upsert: store,
270
282
  }
@@ -16,6 +16,11 @@ import { useRecurringPipelinesStore } from '~/stores/recurringPipelines'
16
16
  import { useServicesStore } from '~/stores/services'
17
17
  import { useAgentsStore } from '~/stores/agents'
18
18
  import { useTrackerStore } from '~/stores/tracker'
19
+ import { useRequirementsStore } from '~/stores/requirements'
20
+ import { useClarityStore } from '~/stores/clarity'
21
+ import { useBrainstormStore } from '~/stores/brainstorm'
22
+ import { useConsensusStore } from '~/stores/consensus'
23
+ import { useGitHubStore } from '~/stores/github'
19
24
 
20
25
  /**
21
26
  * Owns the active workspace and bootstraps the app against the backend. On load
@@ -58,6 +63,18 @@ export const useWorkspaceStore = defineStore(
58
63
 
59
64
  /** Push a snapshot into the data stores. */
60
65
  function hydrate(snapshot: WorkspaceSnapshot) {
66
+ // A change of active board (or the first load) — drop the per-block caches that are
67
+ // NOT part of the snapshot (reviews, brainstorm/consensus sessions, the GitHub
68
+ // projection) so a switched-to board never shows the previous one's stale state.
69
+ // These are lazily reloaded/re-probed per board, so clearing on a same-board refresh
70
+ // would needlessly wipe an open review window — hence only on an actual id change.
71
+ if (workspaceId.value !== snapshot.workspace.id) {
72
+ useRequirementsStore().reset()
73
+ useClarityStore().reset()
74
+ useBrainstormStore().reset()
75
+ useConsensusStore().reset()
76
+ useGitHubStore().reset()
77
+ }
61
78
  workspaceId.value = snapshot.workspace.id
62
79
  spend.value = snapshot.spend ?? null
63
80
  // Keep the board list in step (e.g. a freshly created board, or a rename).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/app",
3
- "version": "0.37.2",
3
+ "version": "0.38.0",
4
4
  "description": "Reusable Nuxt layer for the Agent Architecture Board SPA (components, stores, composables, pages). Consume it from a thin deployment app via `extends: ['@cat-factory/app']` and point it at your backend with NUXT_PUBLIC_API_BASE. See deploy/frontend for an example.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -32,7 +32,7 @@
32
32
  "pinia-plugin-persistedstate": "^4.7.1",
33
33
  "vue": "^3.5.38",
34
34
  "wretch": "^3.0.9",
35
- "@cat-factory/contracts": "0.36.0"
35
+ "@cat-factory/contracts": "0.37.0"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@toad-contracts/testing": "0.3.1",