@cat-factory/app 0.247.0 → 0.249.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.
- package/app/components/board/AddTaskModal.vue +1 -0
- package/app/components/board/nodes/BlockNode.vue +24 -0
- package/app/components/context/ContextAttachmentFields.vue +102 -0
- package/app/components/context/pastedLinkOffer.logic.spec.ts +35 -0
- package/app/components/context/pastedLinkOffer.logic.ts +50 -0
- package/app/components/documents/DocumentSourceConnectModal.vue +50 -0
- package/app/components/documents/SpawnPreviewModal.vue +92 -16
- package/app/components/documents/StartFromDesignModal.vue +237 -0
- package/app/components/environments/EnvironmentSetupWizard.vue +1 -1
- package/app/components/environments/steps/JourneyStepNav.vue +1 -1
- package/app/components/layout/AccountDeploymentSettings.vue +122 -0
- package/app/components/layout/BoardToolbar.vue +1 -1
- package/app/components/layout/CommandBar.vue +1 -1
- package/app/components/layout/SideBar.vue +2 -2
- package/app/components/merge/MergeEffortChips.vue +1 -1
- package/app/components/panels/InspectorPanel.vue +1 -1
- package/app/components/panels/ResultWindowShell.vue +2 -2
- package/app/components/panels/StepResultViewHost.vue +1 -1
- package/app/components/panels/inspector/TaskRunSettings.vue +1 -1
- package/app/components/settings/MergeClassRulesEditor.vue +1 -1
- package/app/composables/api/documents.ts +17 -6
- package/app/composables/useDocumentSourceConnect.ts +88 -0
- package/app/modular/agent-kinds.ts +1 -1
- package/app/modular/external-tools.spec.ts +1 -0
- package/app/modular/journeys/environmentSetup.logic.ts +1 -1
- package/app/modular/journeys/environmentSetup.ts +1 -1
- package/app/modular/journeys/persistence.ts +1 -1
- package/app/modular/nav-contributions.spec.ts +2 -0
- package/app/modular/nav-contributions.ts +11 -1
- package/app/modular/nav-gates.ts +4 -0
- package/app/modular/panels/inspector.logic.ts +1 -1
- package/app/modular/registry.spec.ts +1 -0
- package/app/modular/registry.ts +1 -1
- package/app/modular/result-views.ts +1 -1
- package/app/modular/slots.ts +1 -1
- package/app/modular/tutorial-tours.spec.ts +5 -0
- package/app/modular/tutorial-tours.ts +77 -0
- package/app/pages/index.vue +4 -0
- package/app/plugins/modular.client.ts +1 -1
- package/app/stores/agents.ts +1 -1
- package/app/stores/documents.spec.ts +60 -0
- package/app/stores/documents.ts +52 -24
- package/app/stores/environmentWizard.ts +1 -1
- package/app/stores/publicApiKeys.spec.ts +1 -0
- package/app/stores/ui/modals.ts +15 -0
- package/app/utils/modular.ts +1 -1
- package/i18n/locales/de.json +82 -6
- package/i18n/locales/en.json +82 -6
- package/i18n/locales/es.json +82 -6
- package/i18n/locales/fr.json +82 -6
- package/i18n/locales/he.json +82 -6
- package/i18n/locales/it.json +82 -6
- package/i18n/locales/ja.json +82 -6
- package/i18n/locales/pl.json +82 -6
- package/i18n/locales/tr.json +82 -6
- package/i18n/locales/uk.json +82 -6
- package/package.json +2 -2
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* The environment-setup journey — the slice-3 pilot of the modular-vue adoption
|
|
21
|
-
* (docs/
|
|
21
|
+
* (backend/docs/adr/0049-modular-vue-adoption.md). It replaces the wizard's hand-rolled
|
|
22
22
|
* `STEP_ORDER` + `step` ref + `goToStep` navigation (in `stores/environmentWizard.ts`)
|
|
23
23
|
* with a typed, back/rewind-capable, resumable journey; the per-step data + async
|
|
24
24
|
* actions stay in that Pinia store, driven by the step components below.
|
|
@@ -5,7 +5,7 @@ import { createPiniaJourneyPersistence } from '@modular-vue/journeys'
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Pinia-backed journey persistence (slice 3 of the modular-vue adoption —
|
|
8
|
-
* docs/
|
|
8
|
+
* backend/docs/adr/0049-modular-vue-adoption.md).
|
|
9
9
|
*
|
|
10
10
|
* A journey's `persistence` adapter is what makes `runtime.start()` mean
|
|
11
11
|
* RESUME: it probes `keyFor(input)` for an in-flight serialized instance and
|
|
@@ -21,6 +21,7 @@ const NO_GATES: NavGates = {
|
|
|
21
21
|
canManageSettings: false,
|
|
22
22
|
githubAvailable: false,
|
|
23
23
|
libraryAvailable: false,
|
|
24
|
+
designSourceConnected: false,
|
|
24
25
|
infrastructureAvailable: false,
|
|
25
26
|
accountsEnabled: false,
|
|
26
27
|
isAccountAdmin: false,
|
|
@@ -42,6 +43,7 @@ const ALL_GATES: NavGates = {
|
|
|
42
43
|
canManageSettings: true,
|
|
43
44
|
githubAvailable: true,
|
|
44
45
|
libraryAvailable: true,
|
|
46
|
+
designSourceConnected: true,
|
|
45
47
|
infrastructureAvailable: true,
|
|
46
48
|
accountsEnabled: true,
|
|
47
49
|
isAccountAdmin: true,
|
|
@@ -10,7 +10,7 @@ export type { AppSlots } from './slots'
|
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* The single nav/command catalog for the layer (slice 1 of the modular-vue
|
|
13
|
-
* adoption — docs/
|
|
13
|
+
* adoption — backend/docs/adr/0049-modular-vue-adoption.md).
|
|
14
14
|
*
|
|
15
15
|
* Every destination is declared ONCE here as data and rendered three ways —
|
|
16
16
|
* `SideBar`, `CommandBar`, `BoardToolbar` — instead of each shell hand-rolling
|
|
@@ -89,6 +89,16 @@ export interface NavGates {
|
|
|
89
89
|
githubAvailable: boolean
|
|
90
90
|
/** The prompt-fragment library integration is enabled. */
|
|
91
91
|
libraryAvailable: boolean
|
|
92
|
+
/**
|
|
93
|
+
* The board has a CONNECTED design source (Figma, Zeplin).
|
|
94
|
+
*
|
|
95
|
+
* Availability, not permission, like `githubAvailable`: the start-from-design affordance and
|
|
96
|
+
* the tour that walks it both point at a frame-header button that only exists once a design
|
|
97
|
+
* source is connected, so a board without one is offered a walkthrough that hunts for a
|
|
98
|
+
* control nobody can see. Connecting is `integrations.manage`, but STARTING from a design is
|
|
99
|
+
* member-tier, so the gate is the connection rather than the permission to make one.
|
|
100
|
+
*/
|
|
101
|
+
designSourceConnected: boolean
|
|
92
102
|
/** An execution/test-env backend is reported (runner pool / environment / local). */
|
|
93
103
|
infrastructureAvailable: boolean
|
|
94
104
|
/** Accounts (auth) are enabled on the deployment. */
|
package/app/modular/nav-gates.ts
CHANGED
|
@@ -22,6 +22,7 @@ export function createNavGates(): NavGates {
|
|
|
22
22
|
const access = useWorkspaceAccess()
|
|
23
23
|
const github = useGitHubStore()
|
|
24
24
|
const library = useFragmentLibraryStore()
|
|
25
|
+
const documents = useDocumentsStore()
|
|
25
26
|
const accounts = useAccountsStore()
|
|
26
27
|
const auth = useAuthStore()
|
|
27
28
|
const providerConnections = useProviderConnectionsStore()
|
|
@@ -90,6 +91,9 @@ export function createNavGates(): NavGates {
|
|
|
90
91
|
get libraryAvailable() {
|
|
91
92
|
return library.available === true
|
|
92
93
|
},
|
|
94
|
+
get designSourceConnected() {
|
|
95
|
+
return documents.connectedDesignSources.length > 0
|
|
96
|
+
},
|
|
93
97
|
get infrastructureAvailable() {
|
|
94
98
|
// `integrations.manage` is required to provision/manage infrastructure, so
|
|
95
99
|
// gate the whole section on it too (a member/viewer would only 403 inside).
|
|
@@ -3,7 +3,7 @@ import type { Block } from '~/types/domain'
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Pure definition of the block-inspector panel group (slice 4 of the modular-vue
|
|
6
|
-
* adoption — docs/
|
|
6
|
+
* adoption — backend/docs/adr/0049-modular-vue-adoption.md).
|
|
7
7
|
*
|
|
8
8
|
* The inspector used to be a 631-line `v-if` fan in `InspectorPanel.vue` that
|
|
9
9
|
* switched its body on the selected block's `level` (frame/module/task/epic/
|
package/app/modular/registry.ts
CHANGED
|
@@ -8,7 +8,7 @@ import type { AppSlots } from '~/modular/slots'
|
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* modular-vue registry for the `@cat-factory/app` layer (slice 0 of the
|
|
11
|
-
* modular-vue adoption — docs/
|
|
11
|
+
* modular-vue adoption — backend/docs/adr/0049-modular-vue-adoption.md).
|
|
12
12
|
*
|
|
13
13
|
* This is the frontend analogue of the backend's public registries
|
|
14
14
|
* (`registerAgentKind`, `registerGate`): a single registry into which the layer
|
|
@@ -25,7 +25,7 @@ import type { ResultViewContribution } from './slots'
|
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* The first-party result-view registry (slice 2 of the modular-vue adoption —
|
|
28
|
-
* docs/
|
|
28
|
+
* backend/docs/adr/0049-modular-vue-adoption.md).
|
|
29
29
|
*
|
|
30
30
|
* Every built-in dedicated result window is contributed as a `ComponentEntry`
|
|
31
31
|
* to the `resultViews` slot instead of living in a hardcoded `Record` in
|
package/app/modular/slots.ts
CHANGED
|
@@ -10,7 +10,7 @@ import type { WorkspaceMetadataFieldDefinition } from './workspace-metadata'
|
|
|
10
10
|
* The layer's aggregated slot map — the single home for every slot key the
|
|
11
11
|
* first-party modules (and consumer deployments) contribute to. Grows one key
|
|
12
12
|
* per converted seam as the modular-vue adoption proceeds
|
|
13
|
-
* (docs/
|
|
13
|
+
* (backend/docs/adr/0049-modular-vue-adoption.md):
|
|
14
14
|
*
|
|
15
15
|
* - `nav` (slice 1) — the nav/command catalog, rendered by the three shells.
|
|
16
16
|
* - `resultViews` (slice 2) — the id → dedicated result-window registry
|
|
@@ -19,6 +19,7 @@ const ALL_GATES: NavGates = {
|
|
|
19
19
|
canManageSettings: true,
|
|
20
20
|
githubAvailable: true,
|
|
21
21
|
libraryAvailable: true,
|
|
22
|
+
designSourceConnected: true,
|
|
22
23
|
infrastructureAvailable: true,
|
|
23
24
|
accountsEnabled: true,
|
|
24
25
|
isAccountAdmin: true,
|
|
@@ -392,6 +393,10 @@ describe('tour availability across the catalog', () => {
|
|
|
392
393
|
// this is the tour the contextual offer raises when one does.
|
|
393
394
|
'diagnose-failure',
|
|
394
395
|
'review-merge',
|
|
396
|
+
// The same loop as a DESIGNER enters it. Offered rather than catalogue-only because on a
|
|
397
|
+
// board with a design source connected it IS the everyday loop, and its own requirement
|
|
398
|
+
// keeps it off every board without one.
|
|
399
|
+
'start-from-design',
|
|
395
400
|
]
|
|
396
401
|
const CATALOGUE_ONLY = [
|
|
397
402
|
'wire-models',
|
|
@@ -152,6 +152,15 @@ export const TUTORIAL_REQUIREMENTS = {
|
|
|
152
152
|
labelKey: 'tutorial.requirements.library',
|
|
153
153
|
met: (gates) => gates.libraryAvailable,
|
|
154
154
|
},
|
|
155
|
+
// The CONNECTION rather than the permission to make one: starting from a design is
|
|
156
|
+
// member-tier, and the frame-header button the tour clicks exists only once some admin has
|
|
157
|
+
// connected a design source. A tour gated on `integrations.manage` would be withheld from
|
|
158
|
+
// exactly the persona it is written for.
|
|
159
|
+
designSource: {
|
|
160
|
+
id: 'design-source',
|
|
161
|
+
labelKey: 'tutorial.requirements.designSource',
|
|
162
|
+
met: (gates) => gates.designSourceConnected,
|
|
163
|
+
},
|
|
155
164
|
} as const satisfies Record<string, TutorialRequirement>
|
|
156
165
|
|
|
157
166
|
export const TUTORIAL_TOURS: readonly TutorialTour[] = [
|
|
@@ -575,6 +584,74 @@ export const TUTORIAL_TOURS: readonly TutorialTour[] = [
|
|
|
575
584
|
},
|
|
576
585
|
],
|
|
577
586
|
},
|
|
587
|
+
{
|
|
588
|
+
id: 'start-from-design',
|
|
589
|
+
order: 55,
|
|
590
|
+
icon: 'i-lucide-frame',
|
|
591
|
+
titleKey: 'tutorial.tours.startFromDesign.title',
|
|
592
|
+
descriptionKey: 'tutorial.tours.startFromDesign.description',
|
|
593
|
+
// The delivery loop as a DESIGNER enters it, which is a different first step from
|
|
594
|
+
// `first-task`: the work starts from a frame in Figma, not from a title someone types. It
|
|
595
|
+
// rides the loop's order (after the run tours, before the platform half) rather than
|
|
596
|
+
// replacing `first-task`, because it ends in the same add-task form and everything after
|
|
597
|
+
// that point is the arc those tours already cover.
|
|
598
|
+
//
|
|
599
|
+
// Offered at launch, unlike the platform tours, and gated on the design source being
|
|
600
|
+
// CONNECTED: on a board with one, this is the everyday loop rather than reference material.
|
|
601
|
+
requires: [
|
|
602
|
+
TUTORIAL_REQUIREMENTS.boardWrite,
|
|
603
|
+
TUTORIAL_REQUIREMENTS.service,
|
|
604
|
+
TUTORIAL_REQUIREMENTS.designSource,
|
|
605
|
+
],
|
|
606
|
+
steps: [
|
|
607
|
+
{
|
|
608
|
+
id: 'intro',
|
|
609
|
+
titleKey: 'tutorial.tours.startFromDesign.steps.intro.title',
|
|
610
|
+
bodyKey: 'tutorial.tours.startFromDesign.steps.intro.body',
|
|
611
|
+
},
|
|
612
|
+
{
|
|
613
|
+
id: 'open',
|
|
614
|
+
target: 'frame-start-from-design',
|
|
615
|
+
advanceOn: 'target-click',
|
|
616
|
+
placement: 'bottom',
|
|
617
|
+
titleKey: 'tutorial.tours.startFromDesign.steps.open.title',
|
|
618
|
+
bodyKey: 'tutorial.tours.startFromDesign.steps.open.body',
|
|
619
|
+
},
|
|
620
|
+
{
|
|
621
|
+
id: 'paste',
|
|
622
|
+
target: 'start-from-design-link',
|
|
623
|
+
// Inside the modal the previous click opens.
|
|
624
|
+
waitForTargetMs: 8000,
|
|
625
|
+
placement: 'bottom',
|
|
626
|
+
titleKey: 'tutorial.tours.startFromDesign.steps.paste.title',
|
|
627
|
+
bodyKey: 'tutorial.tours.startFromDesign.steps.paste.body',
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
// Left to the anchor-skip rather than given a `when`: the resolved card appears only
|
|
631
|
+
// once a link has actually been pasted, and someone walking the tour without one in
|
|
632
|
+
// hand should reach the finish card rather than stall on an input they cannot fill.
|
|
633
|
+
id: 'resolved',
|
|
634
|
+
target: 'start-from-design-resolved',
|
|
635
|
+
waitForTargetMs: 8000,
|
|
636
|
+
placement: 'bottom',
|
|
637
|
+
titleKey: 'tutorial.tours.startFromDesign.steps.resolved.title',
|
|
638
|
+
bodyKey: 'tutorial.tours.startFromDesign.steps.resolved.body',
|
|
639
|
+
},
|
|
640
|
+
{
|
|
641
|
+
id: 'continue',
|
|
642
|
+
target: 'start-from-design-continue',
|
|
643
|
+
advanceOn: 'target-click',
|
|
644
|
+
placement: 'top',
|
|
645
|
+
titleKey: 'tutorial.tours.startFromDesign.steps.continue.title',
|
|
646
|
+
bodyKey: 'tutorial.tours.startFromDesign.steps.continue.body',
|
|
647
|
+
},
|
|
648
|
+
{
|
|
649
|
+
id: 'finish',
|
|
650
|
+
titleKey: 'tutorial.tours.startFromDesign.steps.finish.title',
|
|
651
|
+
bodyKey: 'tutorial.tours.startFromDesign.steps.finish.body',
|
|
652
|
+
},
|
|
653
|
+
],
|
|
654
|
+
},
|
|
578
655
|
// ---------------------------------------------------------------------------------------
|
|
579
656
|
// The platform half. Ordered after the whole delivery loop so the catalogue reads in the
|
|
580
657
|
// order someone meets these things: learn the loop, then the machinery under it.
|
package/app/pages/index.vue
CHANGED
|
@@ -55,6 +55,9 @@ const DocumentTemplatesModal = defineAsyncComponent(
|
|
|
55
55
|
const SpawnPreviewModal = defineAsyncComponent(
|
|
56
56
|
() => import('~/components/documents/SpawnPreviewModal.vue'),
|
|
57
57
|
)
|
|
58
|
+
const StartFromDesignModal = defineAsyncComponent(
|
|
59
|
+
() => import('~/components/documents/StartFromDesignModal.vue'),
|
|
60
|
+
)
|
|
58
61
|
const BootstrapModal = defineAsyncComponent(
|
|
59
62
|
() => import('~/components/bootstrap/BootstrapModal.vue'),
|
|
60
63
|
)
|
|
@@ -472,6 +475,7 @@ watch(
|
|
|
472
475
|
<DocumentImportModal v-if="ui.documentImport" />
|
|
473
476
|
<DocumentTemplatesModal v-if="ui.documentTemplates" />
|
|
474
477
|
<SpawnPreviewModal v-if="ui.spawnPreview" />
|
|
478
|
+
<StartFromDesignModal v-if="ui.startFromDesign" />
|
|
475
479
|
<BootstrapModal v-if="ui.bootstrapOpen" />
|
|
476
480
|
<AddServiceFromRepoModal v-if="ui.addServiceOpen" />
|
|
477
481
|
<GitHubPanel v-if="ui.githubOpen" />
|
|
@@ -20,7 +20,7 @@ import type { Block, CustomAgentKind, CustomTaskType } from '~/types/domain'
|
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Wire the modular-vue registry into the Nuxt app (slice 0 of the modular-vue
|
|
23
|
-
* adoption — docs/
|
|
23
|
+
* adoption — backend/docs/adr/0049-modular-vue-adoption.md).
|
|
24
24
|
*
|
|
25
25
|
* `enforce: 'post'` is load-bearing for the consumer-contribution seam. Nuxt
|
|
26
26
|
* loads layer plugins before the consuming app's plugins within the same enforce
|
package/app/stores/agents.ts
CHANGED
|
@@ -16,7 +16,7 @@ import type { AgentArchetype, AgentKind, AgentKindVariant, CustomAgentKind } fro
|
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* The agent palette catalog (slice 2 of the modular-vue adoption —
|
|
19
|
-
* docs/
|
|
19
|
+
* backend/docs/adr/0049-modular-vue-adoption.md).
|
|
20
20
|
*
|
|
21
21
|
* Reactive union of three sources, none of which mutates the frozen built-in
|
|
22
22
|
* {@link AGENT_BY_KIND} const any more:
|
|
@@ -154,3 +154,63 @@ describe('documents store: manual refresh', () => {
|
|
|
154
154
|
expect(store.freshnessFor('figma', 'file1:1-2')).toBeUndefined()
|
|
155
155
|
})
|
|
156
156
|
})
|
|
157
|
+
|
|
158
|
+
describe('documents store: the OAuth and design halves of the source list', () => {
|
|
159
|
+
/** The source listing the probe reads: descriptors PLUS what this deployment can OAuth. */
|
|
160
|
+
function stubSources(oauthSources: string[]) {
|
|
161
|
+
stubApi({
|
|
162
|
+
listDocumentSources: () =>
|
|
163
|
+
Promise.resolve({
|
|
164
|
+
sources: [
|
|
165
|
+
{
|
|
166
|
+
source: 'figma',
|
|
167
|
+
label: 'Figma',
|
|
168
|
+
icon: 'i',
|
|
169
|
+
credentialFields: [],
|
|
170
|
+
refLabel: '',
|
|
171
|
+
refPlaceholder: '',
|
|
172
|
+
oauth: { scopes: ['file_content:read'] },
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
source: 'notion',
|
|
176
|
+
label: 'Notion',
|
|
177
|
+
icon: 'i',
|
|
178
|
+
credentialFields: [],
|
|
179
|
+
refLabel: '',
|
|
180
|
+
refPlaceholder: '',
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
oauthSources,
|
|
184
|
+
}),
|
|
185
|
+
listDocumentConnections: () =>
|
|
186
|
+
Promise.resolve({ connections: [{ source: 'figma', label: 'Figma', connectedAt: 1 }] }),
|
|
187
|
+
})
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
it('offers OAuth where the deployment has a registered app', async () => {
|
|
191
|
+
stubSources(['figma'])
|
|
192
|
+
const store = useDocumentsStore()
|
|
193
|
+
await store.probe()
|
|
194
|
+
expect(store.canConnectWithOAuth('figma')).toBe(true)
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
it('withholds it where the SOURCE declares an OAuth half but the deployment registered nothing', async () => {
|
|
198
|
+
// Figma declares the half in both cases; only the registered app decides whether the button
|
|
199
|
+
// is offered. Folded onto the descriptor, this case would render a "Connect with Figma" that
|
|
200
|
+
// can only 503.
|
|
201
|
+
stubSources([])
|
|
202
|
+
const store = useDocumentsStore()
|
|
203
|
+
await store.probe()
|
|
204
|
+
expect(store.descriptorFor('figma')?.oauth).toBeDefined()
|
|
205
|
+
expect(store.canConnectWithOAuth('figma')).toBe(false)
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
it('lists only CONNECTED design sources, so no affordance opens onto a dead end', async () => {
|
|
209
|
+
stubSources(['figma'])
|
|
210
|
+
const store = useDocumentsStore()
|
|
211
|
+
await store.probe()
|
|
212
|
+
// Notion is connectable and not a design source; Figma is both connected and a design one.
|
|
213
|
+
// The classification comes from contracts, so adding a source cannot leave it unclassified.
|
|
214
|
+
expect(store.connectedDesignSources).toEqual(['figma'])
|
|
215
|
+
})
|
|
216
|
+
})
|
package/app/stores/documents.ts
CHANGED
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
} from '~/types/domain'
|
|
15
15
|
import { isConnectableSource } from '@cat-factory/contracts'
|
|
16
16
|
import { useDocumentFreshness } from '~/composables/useDocumentFreshness'
|
|
17
|
+
import { useDocumentSourceConnect } from '~/composables/useDocumentSourceConnect'
|
|
17
18
|
import { useSourceIntegration } from '~/composables/useSourceIntegration'
|
|
18
19
|
import { useUpsertList } from '~/composables/useUpsertList'
|
|
19
20
|
import { useWorkspaceStore } from '~/stores/workspace'
|
|
@@ -32,6 +33,10 @@ export const useDocumentsStore = defineStore('documents', () => {
|
|
|
32
33
|
const api = useApi()
|
|
33
34
|
const workspace = useWorkspaceStore()
|
|
34
35
|
|
|
36
|
+
// What this DEPLOYMENT can OAuth, reported beside the descriptors and read through
|
|
37
|
+
// `useDocumentSourceConnect`, which owns why that is a separate fact from the descriptor's own.
|
|
38
|
+
const oauthSources = ref<DocumentSourceKind[]>([])
|
|
39
|
+
|
|
35
40
|
// Shared opt-in / probe / connections lifecycle (see `useSourceIntegration`).
|
|
36
41
|
const integration = useSourceIntegration<
|
|
37
42
|
DocumentSourceKind,
|
|
@@ -41,16 +46,32 @@ export const useDocumentsStore = defineStore('documents', () => {
|
|
|
41
46
|
enabled: () => !!workspace.workspaceId,
|
|
42
47
|
workspaceId: () => workspace.workspaceId,
|
|
43
48
|
fetch: async () => {
|
|
44
|
-
const [{ sources }, { connections }] = await Promise.all([
|
|
49
|
+
const [{ sources, oauthSources: oauth }, { connections }] = await Promise.all([
|
|
45
50
|
api.listDocumentSources(workspace.requireId()),
|
|
46
51
|
api.listDocumentConnections(workspace.requireId()),
|
|
47
52
|
])
|
|
53
|
+
oauthSources.value = oauth
|
|
48
54
|
return { sources, connections }
|
|
49
55
|
},
|
|
50
56
|
})
|
|
51
57
|
const { available, sources, connections, connectedSources, anyConnected } = integration
|
|
52
58
|
const { descriptorFor, connectionFor, isConnected, probe, ensureProbed } = integration
|
|
53
59
|
|
|
60
|
+
// Connecting a source, and what this board may connect it WITH, in its own collaborator: the
|
|
61
|
+
// question grew several parts when OAuth landed, and none of the store's other concerns (the
|
|
62
|
+
// imported documents, their freshness, the doc-kind role links) read any of them.
|
|
63
|
+
const { canConnectWithOAuth, connectedDesignSources, connect, disconnect, beginOAuthConnect } =
|
|
64
|
+
useDocumentSourceConnect({
|
|
65
|
+
workspaceId: () => workspace.requireId(),
|
|
66
|
+
oauthSources,
|
|
67
|
+
connectedSources,
|
|
68
|
+
onConnected: (conn) => {
|
|
69
|
+
integration.upsertConnection(conn)
|
|
70
|
+
available.value = true
|
|
71
|
+
},
|
|
72
|
+
onDisconnected: (source) => integration.removeConnection(source),
|
|
73
|
+
})
|
|
74
|
+
|
|
54
75
|
const { items: documents, upsert: upsertDoc } = useUpsertList<SourceDocument>({
|
|
55
76
|
key: (d) => `${d.source}:${d.externalId}`,
|
|
56
77
|
prepend: true,
|
|
@@ -76,19 +97,6 @@ export const useDocumentsStore = defineStore('documents', () => {
|
|
|
76
97
|
return documents.value.filter((d) => d.linkedBlockId === blockId)
|
|
77
98
|
}
|
|
78
99
|
|
|
79
|
-
/** Connect the workspace to a source with its credential bag. */
|
|
80
|
-
async function connect(source: DocumentSourceKind, credentials: Record<string, string>) {
|
|
81
|
-
const conn = await api.connectDocumentSource(workspace.requireId(), source, credentials)
|
|
82
|
-
integration.upsertConnection(conn)
|
|
83
|
-
available.value = true
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/** Disconnect the workspace from a source. */
|
|
87
|
-
async function disconnect(source: DocumentSourceKind) {
|
|
88
|
-
await api.disconnectDocumentSource(workspace.requireId(), source)
|
|
89
|
-
integration.removeConnection(source)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
100
|
/** Load the imported documents for the workspace (across sources). */
|
|
93
101
|
async function loadDocuments() {
|
|
94
102
|
documents.value = await api.listDocuments(workspace.requireId())
|
|
@@ -125,20 +133,36 @@ export const useDocumentsStore = defineStore('documents', () => {
|
|
|
125
133
|
return results
|
|
126
134
|
}
|
|
127
135
|
|
|
128
|
-
/**
|
|
129
|
-
|
|
130
|
-
|
|
136
|
+
/**
|
|
137
|
+
* Preview the board structure a page would expand into (no writes).
|
|
138
|
+
*
|
|
139
|
+
* With `frameId` the preview is TARGET-AWARE: the planner is told which service the work goes
|
|
140
|
+
* inside and proposes its modules and tasks instead of an architecture.
|
|
141
|
+
*/
|
|
142
|
+
function plan(
|
|
143
|
+
source: DocumentSourceKind,
|
|
144
|
+
externalId: string,
|
|
145
|
+
frameId?: string,
|
|
146
|
+
): Promise<DocumentBoardPlan> {
|
|
147
|
+
return api.planDocument(workspace.requireId(), source, {
|
|
148
|
+
externalId,
|
|
149
|
+
...(frameId ? { frameId } : {}),
|
|
150
|
+
})
|
|
131
151
|
}
|
|
132
152
|
|
|
133
153
|
/**
|
|
134
|
-
* Apply a page's structure to the board
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
154
|
+
* Apply a page's structure to the board, then refresh the board snapshot.
|
|
155
|
+
*
|
|
156
|
+
* `frameId` must be the SAME frame the preview was planned for. The endpoint re-plans against
|
|
157
|
+
* it, so a targeted preview and its write agree; sending a frame the preview did not use would
|
|
158
|
+
* flatten a board-wide plan into it and discard the frame titles and types the user approved,
|
|
159
|
+
* which is why this was board-level only until target-aware planning existed.
|
|
139
160
|
*/
|
|
140
|
-
async function spawn(source: DocumentSourceKind, externalId: string) {
|
|
141
|
-
const { result } = await api.spawnDocument(workspace.requireId(), source, {
|
|
161
|
+
async function spawn(source: DocumentSourceKind, externalId: string, frameId?: string) {
|
|
162
|
+
const { result } = await api.spawnDocument(workspace.requireId(), source, {
|
|
163
|
+
externalId,
|
|
164
|
+
...(frameId ? { frameId } : {}),
|
|
165
|
+
})
|
|
142
166
|
await workspace.refresh()
|
|
143
167
|
return result
|
|
144
168
|
}
|
|
@@ -218,6 +242,10 @@ export const useDocumentsStore = defineStore('documents', () => {
|
|
|
218
242
|
return {
|
|
219
243
|
available,
|
|
220
244
|
sources,
|
|
245
|
+
oauthSources,
|
|
246
|
+
canConnectWithOAuth,
|
|
247
|
+
connectedDesignSources,
|
|
248
|
+
beginOAuthConnect,
|
|
221
249
|
connections,
|
|
222
250
|
documents,
|
|
223
251
|
loading,
|
|
@@ -33,7 +33,7 @@ import { createSaveActions } from '~/stores/environmentWizard/save'
|
|
|
33
33
|
// compose provider keys purely on the saved recipe (the build-flag rule). Mirrors the other infra
|
|
34
34
|
// stores' idiom; the flow state is a singleton so the wizard's step children share it.
|
|
35
35
|
//
|
|
36
|
-
// Since slice 3 of the modular-vue adoption (docs/
|
|
36
|
+
// Since slice 3 of the modular-vue adoption (backend/docs/adr/0049-modular-vue-adoption.md) the wizard's
|
|
37
37
|
// step NAVIGATION lives in a modular-vue journey (`app/modular/journeys/environmentSetup.ts`), NOT
|
|
38
38
|
// here — this store no longer holds a `step` / `STEP_ORDER` / `goToStep`. It is purely the per-frame
|
|
39
39
|
// data+action layer the journey's step components drive; `beginForFrame` seeds it when a step first
|
package/app/stores/ui/modals.ts
CHANGED
|
@@ -192,6 +192,11 @@ function createDocumentTaskModals(resetHubReturn: ResetHubReturn) {
|
|
|
192
192
|
// opened standalone, and the modal offers every container on the board.
|
|
193
193
|
const bugHunt = ref<{ source: TaskSourceKind | null; containerId: string | null } | null>(null)
|
|
194
194
|
|
|
195
|
+
// Start-from-design: paste a design link, and the resolved reference is staged onto a new task
|
|
196
|
+
// in `frameId`. `frameId` is always present — the affordance lives on a frame header, and the
|
|
197
|
+
// flow ends in the add-task form, which needs a container to create in.
|
|
198
|
+
const startFromDesign = ref<{ frameId: string } | null>(null)
|
|
199
|
+
|
|
195
200
|
// Add-task modal: the container (service frame or module) a new task is being
|
|
196
201
|
// added to, or null when closed. The user types the title + description; nothing
|
|
197
202
|
// is launched until they explicitly start the created task.
|
|
@@ -266,6 +271,13 @@ function createDocumentTaskModals(resetHubReturn: ResetHubReturn) {
|
|
|
266
271
|
function closeBugHunt() {
|
|
267
272
|
bugHunt.value = null
|
|
268
273
|
}
|
|
274
|
+
function openStartFromDesign(frameId: string) {
|
|
275
|
+
resetHubReturn()
|
|
276
|
+
startFromDesign.value = { frameId }
|
|
277
|
+
}
|
|
278
|
+
function closeStartFromDesign() {
|
|
279
|
+
startFromDesign.value = null
|
|
280
|
+
}
|
|
269
281
|
function openAddTask(containerId: string, prefill: AddTaskPrefill | null = null) {
|
|
270
282
|
addTaskPrefill.value = prefill
|
|
271
283
|
addTaskContainerId.value = containerId
|
|
@@ -301,6 +313,7 @@ function createDocumentTaskModals(resetHubReturn: ResetHubReturn) {
|
|
|
301
313
|
taskConnect,
|
|
302
314
|
taskImport,
|
|
303
315
|
bugHunt,
|
|
316
|
+
startFromDesign,
|
|
304
317
|
addTaskContainerId,
|
|
305
318
|
addTaskPrefill,
|
|
306
319
|
reviewFrictionContext,
|
|
@@ -320,6 +333,8 @@ function createDocumentTaskModals(resetHubReturn: ResetHubReturn) {
|
|
|
320
333
|
closeTaskImport,
|
|
321
334
|
openBugHunt,
|
|
322
335
|
closeBugHunt,
|
|
336
|
+
openStartFromDesign,
|
|
337
|
+
closeStartFromDesign,
|
|
323
338
|
openAddTask,
|
|
324
339
|
closeAddTask,
|
|
325
340
|
openReviewFriction,
|
package/app/utils/modular.ts
CHANGED
|
@@ -6,6 +6,6 @@
|
|
|
6
6
|
* ergonomics as the layer's auto-imported stores and composables.
|
|
7
7
|
*
|
|
8
8
|
* See `app/modular/registry.ts` for the seam itself and
|
|
9
|
-
* docs/
|
|
9
|
+
* backend/docs/adr/0049-modular-vue-adoption.md for the adoption plan.
|
|
10
10
|
*/
|
|
11
11
|
export { registerAppModule } from '~/modular/registry'
|