@owlmeans/viable-common 0.0.25 → 0.0.26
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/README.md +3 -3
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/agent-presentation/SKILL.md +1 -1
- package/agent-meta/skills/viable-common/SKILL.md +109 -8
- package/build/blueprint/case.d.ts.map +1 -1
- package/build/blueprint/case.js +58 -16
- package/build/blueprint/case.js.map +1 -1
- package/build/blueprint/resolve.d.ts.map +1 -1
- package/build/blueprint/resolve.js +3 -0
- package/build/blueprint/resolve.js.map +1 -1
- package/build/blueprint/types.d.ts +44 -2
- package/build/blueprint/types.d.ts.map +1 -1
- package/build/blueprint/types.js +35 -1
- package/build/blueprint/types.js.map +1 -1
- package/build/branding.d.ts +5 -1
- package/build/branding.d.ts.map +1 -1
- package/build/branding.js +6 -0
- package/build/branding.js.map +1 -1
- package/build/connect/consts.d.ts +21 -0
- package/build/connect/consts.d.ts.map +1 -1
- package/build/connect/consts.js +21 -0
- package/build/connect/consts.js.map +1 -1
- package/build/connect/entrypoints.d.ts +15 -1
- package/build/connect/entrypoints.d.ts.map +1 -1
- package/build/connect/entrypoints.js +16 -1
- package/build/connect/entrypoints.js.map +1 -1
- package/build/connect/references.d.ts +14 -1
- package/build/connect/references.d.ts.map +1 -1
- package/build/connect/references.js +4 -0
- package/build/connect/references.js.map +1 -1
- package/build/connect/schemas.d.ts +12 -1
- package/build/connect/schemas.d.ts.map +1 -1
- package/build/connect/schemas.js +27 -1
- package/build/connect/schemas.js.map +1 -1
- package/build/connect/types.d.ts +27 -0
- package/build/connect/types.d.ts.map +1 -1
- package/build/consts.d.ts +1 -1
- package/build/consts.d.ts.map +1 -1
- package/build/consts.js +3 -0
- package/build/consts.js.map +1 -1
- package/build/metadata/types.d.ts +8 -0
- package/build/metadata/types.d.ts.map +1 -1
- package/build/metadata/types.js.map +1 -1
- package/build/planning/helpers.d.ts +2 -1
- package/build/planning/helpers.d.ts.map +1 -1
- package/build/planning/helpers.js +5 -2
- package/build/planning/helpers.js.map +1 -1
- package/build/planning/index.d.ts +1 -0
- package/build/planning/index.d.ts.map +1 -1
- package/build/planning/index.js +1 -0
- package/build/planning/index.js.map +1 -1
- package/build/planning/landing.d.ts +25 -0
- package/build/planning/landing.d.ts.map +1 -0
- package/build/planning/landing.js +38 -0
- package/build/planning/landing.js.map +1 -0
- package/build/planning/schemas.d.ts.map +1 -1
- package/build/planning/schemas.js +13 -0
- package/build/planning/schemas.js.map +1 -1
- package/build/planning/types.d.ts +28 -3
- package/build/planning/types.d.ts.map +1 -1
- package/build/scaffold/consts.d.ts +8 -0
- package/build/scaffold/consts.d.ts.map +1 -1
- package/build/scaffold/consts.js +7 -0
- package/build/scaffold/consts.js.map +1 -1
- package/build/scaffold/schemas.d.ts.map +1 -1
- package/build/scaffold/schemas.js +257 -12
- package/build/scaffold/schemas.js.map +1 -1
- package/build/scaffold/types.d.ts +119 -4
- package/build/scaffold/types.d.ts.map +1 -1
- package/build/skills/catalogue.d.ts.map +1 -1
- package/build/skills/catalogue.js +128 -24
- package/build/skills/catalogue.js.map +1 -1
- package/build/skills/consts.d.ts +5 -0
- package/build/skills/consts.d.ts.map +1 -1
- package/build/skills/consts.js +8 -0
- package/build/skills/consts.js.map +1 -1
- package/build/skills/roles.d.ts.map +1 -1
- package/build/skills/roles.js +11 -5
- package/build/skills/roles.js.map +1 -1
- package/build/types.d.ts +30 -0
- package/build/types.d.ts.map +1 -1
- package/package.json +8 -8
- package/src/blueprint/case.ts +64 -16
- package/src/blueprint/resolve.ts +3 -0
- package/src/blueprint/types.ts +57 -1
- package/src/branding.ts +6 -0
- package/src/connect/consts.ts +22 -0
- package/src/connect/entrypoints.ts +26 -4
- package/src/connect/references.ts +16 -3
- package/src/connect/schemas.ts +33 -4
- package/src/connect/types.ts +28 -0
- package/src/consts.ts +3 -0
- package/src/metadata/types.ts +8 -0
- package/src/planning/helpers.ts +5 -2
- package/src/planning/index.ts +1 -0
- package/src/planning/landing.ts +43 -0
- package/src/planning/schemas.ts +13 -0
- package/src/planning/types.ts +29 -3
- package/src/scaffold/consts.ts +10 -0
- package/src/scaffold/schemas.ts +262 -13
- package/src/scaffold/types.ts +105 -5
- package/src/skills/catalogue.ts +129 -24
- package/src/skills/consts.ts +8 -0
- package/src/skills/roles.ts +11 -5
- package/src/types.ts +30 -0
- package/tests/blueprint.spec.ts +98 -0
- package/tests/branding.spec.ts +21 -1
- package/tests/connect-entrypoints.spec.ts +44 -5
- package/tests/planning.spec.ts +56 -1
- package/tests/scaffold.spec.ts +131 -0
- package/tests/skills.spec.ts +63 -2
package/src/blueprint/types.ts
CHANGED
|
@@ -16,6 +16,12 @@ import type { TopologyDescriptor } from '../topology/types.js'
|
|
|
16
16
|
* Deliberately NOT called a preset: `presets/` in the agent library already means the LLM
|
|
17
17
|
* model/provider configurations, and the two are asked at different moments about different
|
|
18
18
|
* things.
|
|
19
|
+
*
|
|
20
|
+
* Five layers are REQUIRED and say what the project is built WITH; the sixth, `experience`, is
|
|
21
|
+
* OPTIONAL and says what its product should do for the people using it. Optional because a
|
|
22
|
+
* blueprint registered before the layer existed — or a patch serialized into a run row — must keep
|
|
23
|
+
* resolving: every reader asks through a helper that answers the default for an absent layer
|
|
24
|
+
* ({@link landingGatePreferenceOf}).
|
|
19
25
|
*/
|
|
20
26
|
export interface Blueprint {
|
|
21
27
|
id: string
|
|
@@ -25,6 +31,7 @@ export interface Blueprint {
|
|
|
25
31
|
template: TemplateLayer
|
|
26
32
|
createApp: CreateAppLayer
|
|
27
33
|
packages: PackagesLayer
|
|
34
|
+
experience?: ExperienceLayer
|
|
28
35
|
}
|
|
29
36
|
|
|
30
37
|
/**
|
|
@@ -32,7 +39,8 @@ export interface Blueprint {
|
|
|
32
39
|
*
|
|
33
40
|
* The ordering is the point: `technology` overrides everything, because a change of language
|
|
34
41
|
* invalidates every template, prompt and package below it. Nothing today has more than one
|
|
35
|
-
* technology — the ladder exists so that adding one is a data change.
|
|
42
|
+
* technology — the ladder exists so that adding one is a data change. `experience` is last and
|
|
43
|
+
* optional: it shares no key with the five build layers, so its place decides nothing today.
|
|
36
44
|
*/
|
|
37
45
|
export enum BlueprintLayer {
|
|
38
46
|
Technology = 'technology',
|
|
@@ -40,6 +48,7 @@ export enum BlueprintLayer {
|
|
|
40
48
|
Template = 'template',
|
|
41
49
|
CreateApp = 'create-app',
|
|
42
50
|
Packages = 'packages',
|
|
51
|
+
Experience = 'experience',
|
|
43
52
|
}
|
|
44
53
|
|
|
45
54
|
export const BLUEPRINT_LAYER_ORDER: BlueprintLayer[] = [
|
|
@@ -48,8 +57,55 @@ export const BLUEPRINT_LAYER_ORDER: BlueprintLayer[] = [
|
|
|
48
57
|
BlueprintLayer.Template,
|
|
49
58
|
BlueprintLayer.CreateApp,
|
|
50
59
|
BlueprintLayer.Packages,
|
|
60
|
+
BlueprintLayer.Experience,
|
|
51
61
|
]
|
|
52
62
|
|
|
63
|
+
/**
|
|
64
|
+
* How strongly a product of this kind wants a LANDING GATE — the working entry into the key
|
|
65
|
+
* end-user workflow drawn on the guest home, where a guest starts before signing in.
|
|
66
|
+
*
|
|
67
|
+
* A PRIOR handed to the model that decides, never the decision itself: `Encourage` still lets it
|
|
68
|
+
* answer "no gate" for a product with no end-user step a guest could begin, and `Discourage` still
|
|
69
|
+
* lets it choose one when the specification plainly describes such a step.
|
|
70
|
+
*/
|
|
71
|
+
export enum LandingGatePreference {
|
|
72
|
+
/** Most products of this kind have a first-value step a guest can begin — look for it. */
|
|
73
|
+
Encourage = 'encourage',
|
|
74
|
+
/** Neutral: decide from the specification alone. */
|
|
75
|
+
Allow = 'allow',
|
|
76
|
+
/** Products of this kind rarely have one — choose a gate only when the specification asks. */
|
|
77
|
+
Discourage = 'discourage',
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* What the product should do for the people using it, independent of the stack it is built on.
|
|
82
|
+
*
|
|
83
|
+
* A layer of its own rather than a field of `packages`, because it changes no dependency, no
|
|
84
|
+
* capability and no prompt a coder reads — it is asked by the analysis stage about the PRODUCT.
|
|
85
|
+
*/
|
|
86
|
+
export interface ExperienceLayer {
|
|
87
|
+
landingGate: LandingGatePreference
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const LANDING_GATE_PREFERENCES = new Set<string>(Object.values(LandingGatePreference))
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The landing-gate preference of a resolved blueprint.
|
|
94
|
+
*
|
|
95
|
+
* Total: an absent layer, an absent key and a value this deploy does not know (a patch serialized
|
|
96
|
+
* by a newer one) all answer `Allow` — the neutral prior, which leaves the decision to the
|
|
97
|
+
* specification rather than tilting it either way on a value nobody set.
|
|
98
|
+
*/
|
|
99
|
+
export const landingGatePreferenceOf = (
|
|
100
|
+
blueprint?: Pick<Blueprint, 'experience'> | null
|
|
101
|
+
): LandingGatePreference => {
|
|
102
|
+
const value = blueprint?.experience?.landingGate
|
|
103
|
+
|
|
104
|
+
return value != null && LANDING_GATE_PREFERENCES.has(value)
|
|
105
|
+
? value
|
|
106
|
+
: LandingGatePreference.Allow
|
|
107
|
+
}
|
|
108
|
+
|
|
53
109
|
/** Language and toolchain. Overrides everything below it. */
|
|
54
110
|
export interface TechnologyLayer {
|
|
55
111
|
id: string
|
package/src/branding.ts
CHANGED
|
@@ -14,6 +14,7 @@ export const BRANDING_ENV_KEYS = [
|
|
|
14
14
|
'BRANDING_PRIVACY_URL',
|
|
15
15
|
'BRANDING_CREDIT',
|
|
16
16
|
'BRANDING_PRODUCT',
|
|
17
|
+
'BRANDING_GOOGLE_TAG',
|
|
17
18
|
] as const
|
|
18
19
|
|
|
19
20
|
/**
|
|
@@ -23,6 +24,10 @@ export const BRANDING_ENV_KEYS = [
|
|
|
23
24
|
* hides the platform credit, so a metadata set that never carried the key keeps it. That is the
|
|
24
25
|
* safe direction — the opposite failure hands out a paid feature and nobody reports getting more
|
|
25
26
|
* than they paid for.
|
|
27
|
+
*
|
|
28
|
+
* `BRANDING_GOOGLE_TAG` is always EMITTED, empty when no tag is set, although its metadata key is
|
|
29
|
+
* omitted from a push while unset: the environment is read by the application's own build, which
|
|
30
|
+
* treats `''` as "load no tag", while the metadata key is read by a publisher that may predate it.
|
|
26
31
|
*/
|
|
27
32
|
export const brandingEnv = (meta: Partial<SlotMetadata>): Record<string, string> => ({
|
|
28
33
|
BRANDING_COPYRIGHT: meta.brandingCopyright ?? '',
|
|
@@ -31,4 +36,5 @@ export const brandingEnv = (meta: Partial<SlotMetadata>): Record<string, string>
|
|
|
31
36
|
BRANDING_PRIVACY_URL: meta.brandingPrivacyUrl ?? '',
|
|
32
37
|
BRANDING_CREDIT: meta.brandingCredit === '' ? '' : '1',
|
|
33
38
|
BRANDING_PRODUCT: meta.projectName ?? '',
|
|
39
|
+
BRANDING_GOOGLE_TAG: meta.brandingGoogleTag ?? '',
|
|
34
40
|
})
|
package/src/connect/consts.ts
CHANGED
|
@@ -301,6 +301,19 @@ export const CONNECT_CONFIGURE_TIMEOUT_MS = 60_000
|
|
|
301
301
|
*/
|
|
302
302
|
export const CONNECT_CAP_LOCAL_LLM_KEY = 'connect--local-llm'
|
|
303
303
|
|
|
304
|
+
/**
|
|
305
|
+
* The ceilings of a project's branding strings on the connector wire.
|
|
306
|
+
*
|
|
307
|
+
* Each equals its twin in the platform's own branding contract (`BRANDING_COPYRIGHT_MAX`,
|
|
308
|
+
* `BRANDING_ORGANIZATION_MAX`, `BRANDING_URL_MAX`) and must stay equal: a connector refused below the
|
|
309
|
+
* web form's limit, or accepted above it and refused further in, is one value with two answers.
|
|
310
|
+
* The Google tag's is its own — the longest id any Google product issues is well under it.
|
|
311
|
+
*/
|
|
312
|
+
export const CONNECT_BRANDING_COPYRIGHT_MAX = 200
|
|
313
|
+
export const CONNECT_BRANDING_ORGANIZATION_MAX = 120
|
|
314
|
+
export const CONNECT_BRANDING_URL_MAX = 2048
|
|
315
|
+
export const CONNECT_BRANDING_GOOGLE_TAG_MAX = 32
|
|
316
|
+
|
|
304
317
|
/** The access-token prefix the platform issues. Every connector token starts with it. */
|
|
305
318
|
export const CONNECT_TOKEN_PREFIX = 'vib_'
|
|
306
319
|
|
|
@@ -393,6 +406,15 @@ export const connect = Object.freeze({
|
|
|
393
406
|
llm: 'viable:manager-api:connect:project:llm',
|
|
394
407
|
/** The per-project converter inference mode. Separate from `llm`: it is not a paid capability. */
|
|
395
408
|
converterLlm: 'viable:manager-api:connect:project:converter-llm',
|
|
409
|
+
/**
|
|
410
|
+
* The project's own branding — copyright, organization, the two legal links, the Google tag.
|
|
411
|
+
* The platform credit is deliberately NOT here: hiding it is a paid capability with its own
|
|
412
|
+
* gated route, and a connector setting the rest must never be able to touch it.
|
|
413
|
+
*/
|
|
414
|
+
branding: Object.freeze({
|
|
415
|
+
get: 'viable:manager-api:connect:project:branding:get',
|
|
416
|
+
save: 'viable:manager-api:connect:project:branding:save',
|
|
417
|
+
}),
|
|
396
418
|
}),
|
|
397
419
|
story: Object.freeze({
|
|
398
420
|
status: 'viable:manager-api:connect:story:status',
|
|
@@ -7,14 +7,15 @@ import {
|
|
|
7
7
|
ConnectAttachBodySchema, ConnectConfirmBodySchema, ConnectConvertCreateBodySchema,
|
|
8
8
|
ConnectConvertProceedBodySchema, ConnectCreateBodySchema, ConnectInquiryParamsSchema,
|
|
9
9
|
ConnectModifyBodySchema, ConnectOpParamsSchema, ConnectOpResultSchema,
|
|
10
|
-
ConnectPipelineParamsSchema, ConnectPipelineResumeBodySchema,
|
|
11
|
-
ConnectProjectLlmBodySchema, ConnectSessionOpenSchema,
|
|
12
|
-
ConnectPullQuerySchema, ConnectStoryParamsSchema, InquiryAnswerSchema,
|
|
10
|
+
ConnectPipelineParamsSchema, ConnectPipelineResumeBodySchema, ConnectProjectBrandingSaveSchema,
|
|
11
|
+
ConnectProjectIdSchema, ConnectProjectLlmBodySchema, ConnectSessionOpenSchema,
|
|
12
|
+
ConnectSessionParamsSchema, ConnectPullQuerySchema, ConnectStoryParamsSchema, InquiryAnswerSchema,
|
|
13
13
|
} from './schemas.js'
|
|
14
14
|
import type {
|
|
15
15
|
ConnectAttachBody, ConnectConfirmBody, ConnectConvertCreateBody, ConnectConvertProceedBody,
|
|
16
16
|
ConnectCreateBody, ConnectInquiryAnswerBody, ConnectModifyBody,
|
|
17
|
-
ConnectPipelineParams, ConnectPipelineResumeBody,
|
|
17
|
+
ConnectPipelineParams, ConnectPipelineResumeBody, ConnectProjectBranding,
|
|
18
|
+
ConnectProjectBrandingSave, ConnectProjectLlmBody, ConnectSessionOpen,
|
|
18
19
|
ConnectPipelineState, ConnectProjectStatus, ConnectPullQuery, ConnectSessionParams,
|
|
19
20
|
ConnectStoryStatus, ConversionStatusView, ConvertCheck,
|
|
20
21
|
} from './types.js'
|
|
@@ -188,6 +189,27 @@ export const connectProtocols = (opts: ConnectEntrypointOptions) => {
|
|
|
188
189
|
body: typed<ConverterProjectLlmBody>(ConverterProjectLlmBodySchema),
|
|
189
190
|
}, typed()),
|
|
190
191
|
),
|
|
192
|
+
// Under `base` like every sibling — the guard and the ownership gate — and never under the
|
|
193
|
+
// paid gate: saving branding is free, and the paid credit switch is not reachable from here.
|
|
194
|
+
branding: {
|
|
195
|
+
get: protocol(
|
|
196
|
+
route(connect.project.branding.get, '/project/:id/branding', {
|
|
197
|
+
parent: base, method: RouteMethod.GET,
|
|
198
|
+
}),
|
|
199
|
+
contract.request({
|
|
200
|
+
params: typed<{ id: string }>(ConnectProjectIdSchema),
|
|
201
|
+
}, typed<ConnectProjectBranding>()),
|
|
202
|
+
),
|
|
203
|
+
save: protocol(
|
|
204
|
+
route(connect.project.branding.save, '/project/:id/branding', {
|
|
205
|
+
parent: base, method: RouteMethod.POST,
|
|
206
|
+
}),
|
|
207
|
+
contract.request({
|
|
208
|
+
params: typed<{ id: string }>(ConnectProjectIdSchema),
|
|
209
|
+
body: typed<ConnectProjectBrandingSave>(ConnectProjectBrandingSaveSchema),
|
|
210
|
+
}, typed<ConnectProjectBranding>()),
|
|
211
|
+
),
|
|
212
|
+
},
|
|
191
213
|
},
|
|
192
214
|
|
|
193
215
|
story: {
|
|
@@ -8,9 +8,10 @@ import type {
|
|
|
8
8
|
ConnectAttachBody, ConnectCapabilitiesView, ConnectConfirmBody, ConnectCreateBody,
|
|
9
9
|
ConnectConvertCreateBody, ConnectConvertProceedBody, ConnectInquiryAnswerBody,
|
|
10
10
|
ConnectModifyBody, ConnectOpSubmission, ConnectPipelineParams, ConnectPipelineResumeBody,
|
|
11
|
-
ConnectPipelineState,
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
ConnectPipelineState, ConnectProjectBranding, ConnectProjectBrandingSave, ConnectProjectLlmBody,
|
|
12
|
+
ConnectProjectSettings, ConnectProjectStatus, ConnectProjectSummary, ConnectPullQuery,
|
|
13
|
+
ConnectSessionOpen, ConnectSessionParams, ConnectSessionView, ConnectStoryStatus,
|
|
14
|
+
ConversionStatusView, ConvertCheck,
|
|
14
15
|
} from './types.js'
|
|
15
16
|
|
|
16
17
|
type ConnectReference<Request extends RequestShape, Response> =
|
|
@@ -44,6 +45,12 @@ export interface ConnectReferences {
|
|
|
44
45
|
converterLlm: ConnectReference<{
|
|
45
46
|
params: { id: string }, body: ConverterProjectLlmBody
|
|
46
47
|
}, ConnectProjectSettings>
|
|
48
|
+
branding: {
|
|
49
|
+
get: ConnectReference<{ params: { id: string } }, ConnectProjectBranding>
|
|
50
|
+
save: ConnectReference<{
|
|
51
|
+
params: { id: string }, body: ConnectProjectBrandingSave
|
|
52
|
+
}, ConnectProjectBranding>
|
|
53
|
+
}
|
|
47
54
|
}
|
|
48
55
|
story: {
|
|
49
56
|
status: ConnectReference<{ params: { id: string, storyId: string } }, ConnectStoryStatus>
|
|
@@ -110,6 +117,12 @@ export const connectRef: ConnectReferences = {
|
|
|
110
117
|
converterLlm: entrypointRef<{
|
|
111
118
|
params: { id: string }, body: ConverterProjectLlmBody
|
|
112
119
|
}, ConnectProjectSettings>(connect.project.converterLlm),
|
|
120
|
+
branding: {
|
|
121
|
+
get: entrypointRef<{ params: { id: string } }, ConnectProjectBranding>(connect.project.branding.get),
|
|
122
|
+
save: entrypointRef<{
|
|
123
|
+
params: { id: string }, body: ConnectProjectBrandingSave
|
|
124
|
+
}, ConnectProjectBranding>(connect.project.branding.save),
|
|
125
|
+
},
|
|
113
126
|
},
|
|
114
127
|
story: {
|
|
115
128
|
status: entrypointRef<{
|
package/src/connect/schemas.ts
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import type { JSONSchemaType } from 'ajv'
|
|
2
2
|
import { ConversionDecision, OriginKind } from '../convert/consts.js'
|
|
3
3
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
CONNECT_BRANDING_COPYRIGHT_MAX, CONNECT_BRANDING_GOOGLE_TAG_MAX,
|
|
5
|
+
CONNECT_BRANDING_ORGANIZATION_MAX, CONNECT_BRANDING_URL_MAX, CONNECT_INQUIRY_MAX_TEXT,
|
|
6
|
+
ConnectHarness, ConnectLlm, ConnectOpErrorKind, ConnectSessionStatus, ConnectTarget,
|
|
7
|
+
ConnectTransport, ModelTaskResultKind, ModelTier
|
|
6
8
|
} from './consts.js'
|
|
7
9
|
import type { ConnectOpResult, InquiryAnswerPayload } from './ops.js'
|
|
8
10
|
import type {
|
|
9
11
|
ConnectAttachBody, ConnectConfirmBody, ConnectConvertCreateBody, ConnectConvertProceedBody,
|
|
10
12
|
ConnectCreateBody, ConnectLlmBody, ConnectModifyBody, ConnectPipelineParams,
|
|
11
|
-
ConnectPipelineResumeBody, ConnectProjectLlmBody, ConnectSession,
|
|
12
|
-
ConnectPullQuery, ConnectSessionParams
|
|
13
|
+
ConnectPipelineResumeBody, ConnectProjectBrandingSave, ConnectProjectLlmBody, ConnectSession,
|
|
14
|
+
ConnectSessionOpen, ConnectPullQuery, ConnectSessionParams
|
|
13
15
|
} from './types.js'
|
|
14
16
|
|
|
15
17
|
/**
|
|
@@ -313,6 +315,33 @@ export const ConnectProjectLlmBodySchema = {
|
|
|
313
315
|
additionalProperties: false,
|
|
314
316
|
} as unknown as JSONSchemaType<ConnectProjectLlmBody>
|
|
315
317
|
|
|
318
|
+
/**
|
|
319
|
+
* The branding patch a connector saves.
|
|
320
|
+
*
|
|
321
|
+
* STRUCTURAL only — lengths and types. What makes a value acceptable (a non-empty copyright and
|
|
322
|
+
* organization, a legal link that is `https://` or a same-origin path, a real Google tag id) is the
|
|
323
|
+
* platform's rule, applied by the handler to the MERGED record with the same checks the web form
|
|
324
|
+
* uses; a pattern repeated here would be a second copy of those rules free to drift from the first.
|
|
325
|
+
* No `pattern`, and no `enum` on the tag: a connector and the platform deploy apart, and a tag kind
|
|
326
|
+
* Google adds later must be refused by the rule that knows about it, not by a stale wire schema.
|
|
327
|
+
*/
|
|
328
|
+
export const ConnectProjectBrandingSaveSchema = {
|
|
329
|
+
type: 'object',
|
|
330
|
+
properties: {
|
|
331
|
+
copyright: {
|
|
332
|
+
type: 'string', minLength: 1, maxLength: CONNECT_BRANDING_COPYRIGHT_MAX, nullable: true,
|
|
333
|
+
},
|
|
334
|
+
organizationName: {
|
|
335
|
+
type: 'string', minLength: 1, maxLength: CONNECT_BRANDING_ORGANIZATION_MAX, nullable: true,
|
|
336
|
+
},
|
|
337
|
+
termsUrl: { type: 'string', maxLength: CONNECT_BRANDING_URL_MAX, nullable: true },
|
|
338
|
+
privacyUrl: { type: 'string', maxLength: CONNECT_BRANDING_URL_MAX, nullable: true },
|
|
339
|
+
googleTag: { type: 'string', maxLength: CONNECT_BRANDING_GOOGLE_TAG_MAX, nullable: true },
|
|
340
|
+
},
|
|
341
|
+
required: [],
|
|
342
|
+
additionalProperties: false,
|
|
343
|
+
} as unknown as JSONSchemaType<ConnectProjectBrandingSave>
|
|
344
|
+
|
|
316
345
|
export const ModelTaskResultSchema = {
|
|
317
346
|
type: 'object',
|
|
318
347
|
properties: {
|
package/src/connect/types.ts
CHANGED
|
@@ -223,6 +223,34 @@ export interface ConnectProjectLlmBody {
|
|
|
223
223
|
llmMode: ConnectLlm | null
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
+
/**
|
|
227
|
+
* A project's branding as a connector reads it — what the generated application says about who
|
|
228
|
+
* made it, and the Google tag it loads.
|
|
229
|
+
*
|
|
230
|
+
* Every field is a string, `''` when unset, in the platform's own branding vocabulary. The
|
|
231
|
+
* platform credit is absent on purpose: hiding it is a paid capability behind its own gated route,
|
|
232
|
+
* and neither reading nor saving this record may reach it.
|
|
233
|
+
*/
|
|
234
|
+
export interface ConnectProjectBranding {
|
|
235
|
+
/** The copyright line in the application's footer. Never empty once saved. */
|
|
236
|
+
copyright: string
|
|
237
|
+
/** The organization's display name. Never empty once saved. */
|
|
238
|
+
organizationName: string
|
|
239
|
+
/** `''`, an `https://` URL, or a same-origin path such as `/terms` (the generated page). */
|
|
240
|
+
termsUrl: string
|
|
241
|
+
/** `''`, an `https://` URL, or a same-origin path such as `/privacy` (the generated page). */
|
|
242
|
+
privacyUrl: string
|
|
243
|
+
/** `''`, or a Google tag id: `GTM-…`, `G-…`, `GT-…`, `AW-…`, `DC-…`. */
|
|
244
|
+
googleTag: string
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* A PATCH of a project's branding: every field optional, and an absent field keeps its current
|
|
249
|
+
* value. The platform merges it over what is stored, validates the result as a whole with the
|
|
250
|
+
* rules the web form uses, and answers the merged record.
|
|
251
|
+
*/
|
|
252
|
+
export interface ConnectProjectBrandingSave extends Partial<ConnectProjectBranding> {}
|
|
253
|
+
|
|
226
254
|
/** What the platform tells a connector about itself. */
|
|
227
255
|
export interface ConnectCapabilitiesView {
|
|
228
256
|
/** Tier → the platform role names that will be asked for at that tier. */
|
package/src/consts.ts
CHANGED
|
@@ -17,6 +17,9 @@ export const metadataConfigs = [
|
|
|
17
17
|
'brandingPrivacyUrl',
|
|
18
18
|
'brandingCredit',
|
|
19
19
|
'brandingHideCreditIntent',
|
|
20
|
+
// Optional in `SlotConstMetadata`: omitted from a push while unset (an older publisher refuses
|
|
21
|
+
// an undeclared key), so an empty stored row must never be delivered as `''`.
|
|
22
|
+
'brandingGoogleTag',
|
|
20
23
|
] satisfies (keyof NonSecretScalarSlotMetadata)[]
|
|
21
24
|
|
|
22
25
|
export const metadataLists = [
|
package/src/metadata/types.ts
CHANGED
|
@@ -116,6 +116,12 @@ export interface StoryMeta extends Record<string, unknown> {
|
|
|
116
116
|
* that moves section when it is implemented leaves its own dashboard behind.
|
|
117
117
|
*/
|
|
118
118
|
section?: string
|
|
119
|
+
/**
|
|
120
|
+
* This is the landing gate's story — a guest starts it on the landing page and continues here
|
|
121
|
+
* after signing in. Copied from the card's `fields.landing`; written only when `true`, so every
|
|
122
|
+
* other story's file keeps the shape it always had.
|
|
123
|
+
*/
|
|
124
|
+
landing?: boolean
|
|
119
125
|
entity: string
|
|
120
126
|
entities: string[]
|
|
121
127
|
screens: StoryScreenRef[]
|
|
@@ -153,6 +159,8 @@ export interface StoryWriteInput {
|
|
|
153
159
|
narrative: string
|
|
154
160
|
status: string
|
|
155
161
|
primary: boolean
|
|
162
|
+
/** The card's landing flag — see {@link StoryMeta.landing}. Absent reads as `false`. */
|
|
163
|
+
landing?: boolean
|
|
156
164
|
/** Carried through a develop run so the rewrite does not drop the scaffold's assignment. */
|
|
157
165
|
section?: string
|
|
158
166
|
/** Likewise the reserved screen name. */
|
package/src/planning/helpers.ts
CHANGED
|
@@ -115,19 +115,22 @@ export const projectBriefOf = (project: Workcard, specs: readonly Specification[
|
|
|
115
115
|
* What the metadata store writes to `docs/stories/<code>.md`, from the card and a run's content.
|
|
116
116
|
*
|
|
117
117
|
* The CODE is the target's only story identifier — a card id never reaches a target file — so a
|
|
118
|
-
* story card without one is refused rather than filed under its id.
|
|
118
|
+
* story card without one is refused rather than filed under its id. The landing flag is carried
|
|
119
|
+
* only when it is set, so every other story's frontmatter keeps the shape it always had.
|
|
119
120
|
*/
|
|
120
121
|
export const storyWriteInputOf = (card: Workcard, content: StoryWriteContent): StoryWriteInput => {
|
|
121
122
|
if (card.code == null || card.code === '') {
|
|
122
123
|
throw new ProjectStoryMissconfigured('no-code')
|
|
123
124
|
}
|
|
124
125
|
const { status, ...rest } = content
|
|
126
|
+
const fields = storyFieldsOf(card)
|
|
125
127
|
|
|
126
128
|
return {
|
|
127
129
|
...rest,
|
|
128
130
|
code: card.code,
|
|
129
131
|
narrative: card.title,
|
|
130
132
|
status: status ?? card.status,
|
|
131
|
-
primary:
|
|
133
|
+
primary: fields.primary,
|
|
134
|
+
...(fields.landing === true ? { landing: true } : {}),
|
|
132
135
|
}
|
|
133
136
|
}
|
package/src/planning/index.ts
CHANGED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { TITLE_MAX } from '@owlmeans/planning'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The sentence a landing story's narrative carries — what a person reading the board sees.
|
|
5
|
+
*
|
|
6
|
+
* The AUTHORITY is the card's `fields.landing` flag, never this text: the design and development
|
|
7
|
+
* stages key on the flag, and a person may reword the narrative at any time. The sentence exists so
|
|
8
|
+
* the board says in plain words why this story is different, and so the analyst and the designer,
|
|
9
|
+
* who read the narrative, write a screen that continues what the guest started.
|
|
10
|
+
*
|
|
11
|
+
* A constant, byte for byte, because {@link hasLandingSentence} is how a resumed decision step
|
|
12
|
+
* knows it already appended it.
|
|
13
|
+
*/
|
|
14
|
+
export const LANDING_STORY_SENTENCE = 'A visitor can start this on the landing page without an account;'
|
|
15
|
+
+ ' after signing in they continue here with their choices carried over.'
|
|
16
|
+
|
|
17
|
+
const normalized = (text: string): string => text.replace(/\s+/g, ' ')
|
|
18
|
+
|
|
19
|
+
/** Whether a narrative already carries the landing sentence, whatever whitespace surrounds it. */
|
|
20
|
+
export const hasLandingSentence = (title: string): boolean =>
|
|
21
|
+
normalized(title).includes(LANDING_STORY_SENTENCE)
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The narrative with the landing sentence appended once.
|
|
25
|
+
*
|
|
26
|
+
* Idempotent — a narrative that already carries the sentence comes back unchanged, so a decision
|
|
27
|
+
* step that is resumed after it wrote the card never appends a second copy. It never produces a
|
|
28
|
+
* title longer than a card accepts (`TITLE_MAX`): a narrative with no room left is returned as it
|
|
29
|
+
* is rather than cut, because a person's words are worth more than the note, and the flag on the
|
|
30
|
+
* card still carries the fact.
|
|
31
|
+
*/
|
|
32
|
+
export const withLandingSentence = (title: string): string => {
|
|
33
|
+
if (hasLandingSentence(title)) {
|
|
34
|
+
return title
|
|
35
|
+
}
|
|
36
|
+
const base = title.trimEnd()
|
|
37
|
+
if (base === '') {
|
|
38
|
+
return LANDING_STORY_SENTENCE
|
|
39
|
+
}
|
|
40
|
+
const joined = `${base} ${LANDING_STORY_SENTENCE}`
|
|
41
|
+
|
|
42
|
+
return joined.length > TITLE_MAX ? title : joined
|
|
43
|
+
}
|
package/src/planning/schemas.ts
CHANGED
|
@@ -28,6 +28,18 @@ export const ViableProjectFieldsSchema = {
|
|
|
28
28
|
origin: { ...ProjectOriginSchema, nullable: true },
|
|
29
29
|
connectLlmMode: { type: 'string', enum: [...Object.values(ConnectLlm), null], nullable: true },
|
|
30
30
|
converterLlmMode: { type: 'string', enum: [...Object.values(ConnectLlm), null], nullable: true },
|
|
31
|
+
// `story: null` is a DECIDED "no gate" and must stay spellable; an absent `landing` is "never
|
|
32
|
+
// decided". `at` carries no `format`: the planning registry compiles without ajv-formats.
|
|
33
|
+
landing: {
|
|
34
|
+
type: 'object',
|
|
35
|
+
nullable: true,
|
|
36
|
+
properties: {
|
|
37
|
+
story: { type: 'string', minLength: 1, maxLength: CODE_MAX, nullable: true },
|
|
38
|
+
at: { type: 'string', minLength: 1, maxLength: 32 },
|
|
39
|
+
},
|
|
40
|
+
required: ['at'],
|
|
41
|
+
additionalProperties: false,
|
|
42
|
+
},
|
|
31
43
|
},
|
|
32
44
|
required: [],
|
|
33
45
|
additionalProperties: false,
|
|
@@ -48,6 +60,7 @@ export const ViableStoryFieldsSchema = {
|
|
|
48
60
|
actor: { type: 'string', enum: [...Object.values(StoryActor), null], nullable: true },
|
|
49
61
|
warning: { type: 'string', maxLength: 1024, nullable: true },
|
|
50
62
|
kind: { type: 'string', enum: [...Object.values(StoryKind), null], nullable: true },
|
|
63
|
+
landing: { type: 'boolean', nullable: true },
|
|
51
64
|
},
|
|
52
65
|
required: ['area', 'primary'],
|
|
53
66
|
additionalProperties: false,
|
package/src/planning/types.ts
CHANGED
|
@@ -32,6 +32,23 @@ export type ViableProjectFields = {
|
|
|
32
32
|
connectLlmMode?: ConnectLlm
|
|
33
33
|
/** Who performs a conversion's model calls on this project; absent inherits. */
|
|
34
34
|
converterLlmMode?: ConnectLlm
|
|
35
|
+
/**
|
|
36
|
+
* The landing-gate decision: which story a guest can start on the landing page.
|
|
37
|
+
*
|
|
38
|
+
* Three states, and the third is why this is an object rather than a code: ABSENT means the
|
|
39
|
+
* decision was never made (an initialization that has not reached it, or a project older than
|
|
40
|
+
* the gate), `story: null` means it WAS made and the answer is "no gate", and a code names the
|
|
41
|
+
* story. A failed decision is never recorded — writing `null` for it would read as a decided
|
|
42
|
+
* "none" and no later run would ask again. `at` is when it was decided, an ISO timestamp.
|
|
43
|
+
*/
|
|
44
|
+
landing?: ViableLandingDecision
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** The recorded landing-gate decision — see {@link ViableProjectFields.landing}. */
|
|
48
|
+
export interface ViableLandingDecision {
|
|
49
|
+
/** The story card's CODE, or `null` for a decided "no gate". */
|
|
50
|
+
story: string | null
|
|
51
|
+
at: string
|
|
35
52
|
}
|
|
36
53
|
|
|
37
54
|
/**
|
|
@@ -51,6 +68,15 @@ export type ViableStoryFields = {
|
|
|
51
68
|
warning?: string
|
|
52
69
|
/** Which half of the analysis the story came from. */
|
|
53
70
|
kind?: StoryKind
|
|
71
|
+
/**
|
|
72
|
+
* The story a guest starts on the landing page — the landing gate's story. At most one per
|
|
73
|
+
* project, and the project card's `fields.landing.story` names the same code.
|
|
74
|
+
*
|
|
75
|
+
* Its development gains a step that replaces the gate's sketch on the guest home with the real
|
|
76
|
+
* component, and its full-scale screen reads the choices the guest carried over. Absent and
|
|
77
|
+
* `false` read the same.
|
|
78
|
+
*/
|
|
79
|
+
landing?: boolean
|
|
54
80
|
}
|
|
55
81
|
|
|
56
82
|
export interface ViableProjectCard extends Project {
|
|
@@ -67,9 +93,9 @@ export type ViableSpecification = Specification
|
|
|
67
93
|
/**
|
|
68
94
|
* What a develop run hands the metadata store beside the card.
|
|
69
95
|
*
|
|
70
|
-
* The code, the narrative and the
|
|
71
|
-
* second copy is a second answer. `status` defaults to the card's own.
|
|
96
|
+
* The code, the narrative, the primary flag and the landing flag are the CARD's and are never
|
|
97
|
+
* passed separately — a second copy is a second answer. `status` defaults to the card's own.
|
|
72
98
|
*/
|
|
73
99
|
export type StoryWriteContent =
|
|
74
|
-
& Omit<StoryWriteInput, 'code' | 'narrative' | 'primary' | 'status'>
|
|
100
|
+
& Omit<StoryWriteInput, 'code' | 'narrative' | 'primary' | 'landing' | 'status'>
|
|
75
101
|
& { status?: string }
|
package/src/scaffold/consts.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { BentoFragmentKind } from './types.js'
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* The shapes a drawn widget can take.
|
|
3
5
|
*
|
|
@@ -24,3 +26,11 @@ export const AREA_HOME_WIDGET_CAP = 3
|
|
|
24
26
|
|
|
25
27
|
/** Sections per area beyond which a top menu stops being navigable. */
|
|
26
28
|
export const AREA_SECTION_CAP = 6
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The shapes a landing page's bento fragment can take.
|
|
32
|
+
*
|
|
33
|
+
* A closed set for the same reason as {@link WidgetKind}: each value is one stamped primitive, so a
|
|
34
|
+
* value nothing can draw is a tile with an empty bottom.
|
|
35
|
+
*/
|
|
36
|
+
export const BENTO_FRAGMENT_KINDS: BentoFragmentKind[] = ['list', 'note', 'people', 'steps']
|