@owlmeans/viable-common 0.0.24 → 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 +4 -4
- 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 +115 -13
- 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 +31 -43
- package/build/connect/consts.d.ts.map +1 -1
- package/build/connect/consts.js +34 -48
- package/build/connect/consts.js.map +1 -1
- package/build/connect/entrypoints.d.ts +36 -19
- package/build/connect/entrypoints.d.ts.map +1 -1
- package/build/connect/entrypoints.js +23 -4
- package/build/connect/entrypoints.js.map +1 -1
- package/build/connect/index.d.ts +0 -1
- package/build/connect/index.d.ts.map +1 -1
- package/build/connect/index.js +0 -1
- package/build/connect/index.js.map +1 -1
- package/build/connect/ops.d.ts +1 -4
- package/build/connect/ops.d.ts.map +1 -1
- package/build/connect/references.d.ts +33 -16
- package/build/connect/references.d.ts.map +1 -1
- package/build/connect/references.js +7 -1
- package/build/connect/references.js.map +1 -1
- package/build/connect/schemas.d.ts +17 -3
- package/build/connect/schemas.d.ts.map +1 -1
- package/build/connect/schemas.js +31 -5
- package/build/connect/schemas.js.map +1 -1
- package/build/connect/types.d.ts +63 -66
- 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/design/runtime.d.ts +1 -1
- package/build/design/runtime.d.ts.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/consts.d.ts +1 -1
- package/build/planning/consts.js +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 +150 -39
- 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 +32 -45
- package/src/connect/entrypoints.ts +51 -23
- package/src/connect/index.ts +0 -1
- package/src/connect/ops.ts +1 -4
- package/src/connect/references.ts +48 -29
- package/src/connect/schemas.ts +40 -11
- package/src/connect/types.ts +66 -69
- package/src/consts.ts +3 -0
- package/src/design/runtime.ts +1 -1
- package/src/metadata/types.ts +8 -0
- package/src/planning/consts.ts +1 -1
- 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 +151 -39
- 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-convert.spec.ts +2 -8
- package/tests/connect-entrypoints.spec.ts +49 -28
- package/tests/planning.spec.ts +56 -1
- package/tests/scaffold.spec.ts +131 -0
- package/tests/skills.spec.ts +63 -2
- package/build/connect/jobs.d.ts +0 -25
- package/build/connect/jobs.d.ts.map +0 -1
- package/build/connect/jobs.js +0 -29
- package/build/connect/jobs.js.map +0 -1
- package/src/connect/jobs.ts +0 -40
package/tests/planning.spec.ts
CHANGED
|
@@ -4,7 +4,7 @@ import addFormats from 'ajv-formats'
|
|
|
4
4
|
import { ResilientError } from '@owlmeans/error'
|
|
5
5
|
import {
|
|
6
6
|
canTransit, CodeScope, CodeStyle, intrinsicOf, IntrinsicStatus, makeSchemaRegistry,
|
|
7
|
-
SpecificationFormat, transitionsFrom, WorkcardKind,
|
|
7
|
+
SpecificationFormat, TITLE_MAX, transitionsFrom, WorkcardKind,
|
|
8
8
|
} from '@owlmeans/planning'
|
|
9
9
|
import type { Specification } from '@owlmeans/planning'
|
|
10
10
|
import { ProjectArea } from '../src/areas/consts.js'
|
|
@@ -14,6 +14,9 @@ import { STORY_DESIGN_VERSION } from '../src/design/consts.js'
|
|
|
14
14
|
import { emptyStoryDesign } from '../src/design/helpers.js'
|
|
15
15
|
import { StoryActor } from '../src/design/runtime.js'
|
|
16
16
|
import type { StoryDesign } from '../src/design/types.js'
|
|
17
|
+
import {
|
|
18
|
+
hasLandingSentence, LANDING_STORY_SENTENCE, withLandingSentence,
|
|
19
|
+
} from '../src/index.js'
|
|
17
20
|
import {
|
|
18
21
|
isUserChannel, isViableProject, isViableStory, ProjectAgentOccupied, ProjectNotFound,
|
|
19
22
|
ProjectStoryMissconfigured, projectBriefOf, storyDraftOf, storyWriteInputOf, userStoryOf,
|
|
@@ -255,9 +258,24 @@ describe('viable-common - the viable field schemas', () => {
|
|
|
255
258
|
expect(project({
|
|
256
259
|
formerAliases: ['old-name'], language: 'en', blueprint: 'owlmeans', blueprintCase: 'web',
|
|
257
260
|
target: null, connectLlmMode: 'local', origin: { kind: 'github', repoUrl: 'https://x/y' },
|
|
261
|
+
landing: { story: 'US-ABC12', at },
|
|
258
262
|
})).toBe(true)
|
|
259
263
|
expect(project({ alias: 'slot-booker' })).toBe(false)
|
|
260
264
|
})
|
|
265
|
+
|
|
266
|
+
test('a story carries the landing flag, spelled or nulled', () => {
|
|
267
|
+
expect(story({ area: ProjectArea.User, primary: false, landing: true })).toBe(true)
|
|
268
|
+
expect(story({ area: ProjectArea.User, primary: false, landing: null })).toBe(true)
|
|
269
|
+
expect(story({ area: ProjectArea.User, primary: false, landing: 'yes' })).toBe(false)
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
test('a project records a decided "no gate" as story null, and never a decision without a time', () => {
|
|
273
|
+
// Absent = never decided; `story: null` = decided, no gate. Both must be spellable.
|
|
274
|
+
expect(project({ landing: { story: null, at } })).toBe(true)
|
|
275
|
+
expect(project({ landing: null })).toBe(true)
|
|
276
|
+
expect(project({ landing: { story: 'US-ABC12' } })).toBe(false)
|
|
277
|
+
expect(project({ landing: { story: 'US-ABC12', at, reason: 'best fit' } })).toBe(false)
|
|
278
|
+
})
|
|
261
279
|
})
|
|
262
280
|
|
|
263
281
|
describe('viable-common - the viable card helpers', () => {
|
|
@@ -311,6 +329,16 @@ describe('viable-common - the viable card helpers', () => {
|
|
|
311
329
|
.toThrow(ProjectStoryMissconfigured)
|
|
312
330
|
})
|
|
313
331
|
|
|
332
|
+
test('storyWriteInputOf copies the landing flag from the card, and only when it is set', () => {
|
|
333
|
+
const content = {
|
|
334
|
+
userStory: userStoryOf(storyCard()), screenPaths: {}, componentPaths: {}, transitions: [],
|
|
335
|
+
}
|
|
336
|
+
const landing = storyCard({ fields: { area: ProjectArea.User, primary: false, landing: true } })
|
|
337
|
+
|
|
338
|
+
expect(storyWriteInputOf(landing, content).landing).toBe(true)
|
|
339
|
+
expect('landing' in storyWriteInputOf(storyCard(), content)).toBe(false)
|
|
340
|
+
})
|
|
341
|
+
|
|
314
342
|
test('only a person writes through the web and connect channels', () => {
|
|
315
343
|
expect(isUserChannel(ViableChannel.Web)).toBe(true)
|
|
316
344
|
expect(isUserChannel(ViableChannel.Connect)).toBe(true)
|
|
@@ -319,6 +347,33 @@ describe('viable-common - the viable card helpers', () => {
|
|
|
319
347
|
})
|
|
320
348
|
})
|
|
321
349
|
|
|
350
|
+
describe('viable-common - the landing sentence', () => {
|
|
351
|
+
test('is appended once, with one space, and a resumed step never adds a second copy', () => {
|
|
352
|
+
const once = withLandingSentence('A member books a slot.')
|
|
353
|
+
|
|
354
|
+
expect(once).toBe(`A member books a slot. ${LANDING_STORY_SENTENCE}`)
|
|
355
|
+
expect(withLandingSentence(once)).toBe(once)
|
|
356
|
+
expect(hasLandingSentence(once)).toBe(true)
|
|
357
|
+
expect(hasLandingSentence('A member books a slot.')).toBe(false)
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
test('is recognised whatever whitespace a person left around it', () => {
|
|
361
|
+
const rewrapped = `A member books a slot.\n${LANDING_STORY_SENTENCE.replace('; after', ';\n after')}`
|
|
362
|
+
|
|
363
|
+
expect(hasLandingSentence(rewrapped)).toBe(true)
|
|
364
|
+
expect(withLandingSentence(rewrapped)).toBe(rewrapped)
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
test('never makes a title longer than a card accepts, and never cuts the narrative', () => {
|
|
368
|
+
const long = 'x'.repeat(TITLE_MAX - 10)
|
|
369
|
+
|
|
370
|
+
expect(withLandingSentence(long)).toBe(long)
|
|
371
|
+
expect(withLandingSentence('x'.repeat(TITLE_MAX - LANDING_STORY_SENTENCE.length - 1)).length)
|
|
372
|
+
.toBe(TITLE_MAX)
|
|
373
|
+
expect(withLandingSentence(' ')).toBe(LANDING_STORY_SENTENCE)
|
|
374
|
+
})
|
|
375
|
+
})
|
|
376
|
+
|
|
322
377
|
describe('viable-common - the follows anchor', () => {
|
|
323
378
|
test('mergeConnectingStories carries the clamped anchor on each connective entry', () => {
|
|
324
379
|
const flow = [
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import Ajv from 'ajv'
|
|
3
|
+
import { validateSpecificationBody } from '@owlmeans/planning'
|
|
4
|
+
import { ProjectArea } from '../src/areas/consts.js'
|
|
5
|
+
import { ViableSpecCategory, VIABLE_PROJECT_SLOTS } from '../src/planning/index.js'
|
|
6
|
+
import { BENTO_FRAGMENT_KINDS, WidgetKind } from '../src/scaffold/consts.js'
|
|
7
|
+
import { ScaffoldPlanSchema } from '../src/scaffold/schemas.js'
|
|
8
|
+
import type { ScaffoldPlan } from '../src/scaffold/types.js'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The scaffold plan's schema is TWO things at once: the shape a model answers with, and the
|
|
12
|
+
* validator of the project card's `scaffold` document. A plan written before the landing-page
|
|
13
|
+
* overhaul carries none of its fields, and must still pass — or that project's plan can never be
|
|
14
|
+
* revised again. Compiled the way the planning registry compiles it (no formats, not strict).
|
|
15
|
+
*/
|
|
16
|
+
const oldPlan = (): ScaffoldPlan => ({
|
|
17
|
+
identity: { name: 'Crumb & Crust', description: 'Recipes and bakes from a welcoming community.' },
|
|
18
|
+
guestHome: {
|
|
19
|
+
hero: { headline: 'Find your next bake.', sub: 'Photo-led recipes.', cta: 'Get started', eyebrow: 'New' },
|
|
20
|
+
problem: { title: 'Inspiration lacks detail', text: 'A bake is easy to find.' },
|
|
21
|
+
solution: { title: 'Bakes worth making', text: 'The full process in one place.' },
|
|
22
|
+
features: [{ title: 'Complete recipes', text: 'Clear ingredient lists.' }],
|
|
23
|
+
testimonials: [{ quote: 'I found a rye loaf.', name: 'Mara L.', role: 'Home baker' }],
|
|
24
|
+
},
|
|
25
|
+
areas: [{ area: ProjectArea.Guest, intro: { title: 'Welcome', text: 'Browse.' }, sections: ['Bakes'] }],
|
|
26
|
+
stories: [{
|
|
27
|
+
code: 'US-ABC12',
|
|
28
|
+
section: 'Bakes',
|
|
29
|
+
screen: 'Bakes/Find a bake',
|
|
30
|
+
widget: { kind: WidgetKind.List, title: 'Bake finder', description: 'Bakes you can make.' },
|
|
31
|
+
sketch: { headline: 'Your bakes', caption: 'What you can bake', items: ['Rye loaf'] },
|
|
32
|
+
home: true,
|
|
33
|
+
}],
|
|
34
|
+
motifs: 'A rye loaf on a board.',
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const newPlan = (): ScaffoldPlan => {
|
|
38
|
+
const plan = oldPlan()
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
...plan,
|
|
42
|
+
guestHome: {
|
|
43
|
+
...plan.guestHome,
|
|
44
|
+
hero: {
|
|
45
|
+
headline: 'Find your next bake.', sub: 'Photo-led recipes.', cta: 'Get started',
|
|
46
|
+
secondary: { label: 'Browse bakes' },
|
|
47
|
+
browse: { label: 'Just browsing? Explore bakes', href: '#features' },
|
|
48
|
+
},
|
|
49
|
+
features: [
|
|
50
|
+
{
|
|
51
|
+
title: 'Complete recipes', text: 'Clear ingredient lists.',
|
|
52
|
+
fragment: {
|
|
53
|
+
kind: 'list', title: 'INGREDIENTS · 1 LOAF',
|
|
54
|
+
rows: [{ label: 'Rye flour', value: '350 g' }, { label: 'Water', value: '390 g' }],
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
{ title: 'Baking notes', text: 'Small adjustments.', fragment: { kind: 'note', rows: [{ label: 'Drop the oven.' }] } },
|
|
58
|
+
{ title: 'Photo-led bakes', text: 'Every idea starts with the bake.' },
|
|
59
|
+
{ title: 'Baker attribution', text: 'See who made it.', fragment: { kind: 'people', rows: [{ label: 'Mara L.', value: 'Rye' }] } },
|
|
60
|
+
],
|
|
61
|
+
steps: [
|
|
62
|
+
{ title: 'Pick what you have', text: 'Tap the ingredients.' },
|
|
63
|
+
{ title: 'See what you can bake', text: 'Ranked by your pantry.' },
|
|
64
|
+
{ title: 'Open the full method', text: 'Sign in with your email.' },
|
|
65
|
+
],
|
|
66
|
+
labels: { steps: 'How it works', stepsTitle: 'From pantry to basket.', problem: 'The problem' },
|
|
67
|
+
closing: {
|
|
68
|
+
headline: 'Your next bake starts here.', lead: 'Browse freely.', primary: 'Get started',
|
|
69
|
+
secondary: { label: 'Browse bakes' },
|
|
70
|
+
links: [{ title: 'Explore bakes', text: 'Open to everyone.' }, { title: 'Share your bake', text: 'Post it.', href: '#' }],
|
|
71
|
+
},
|
|
72
|
+
gate: {
|
|
73
|
+
story: 'US-ABC12',
|
|
74
|
+
target: 'web:bake-finder',
|
|
75
|
+
question: "What's in your pantry?",
|
|
76
|
+
hint: 'No account needed',
|
|
77
|
+
label: 'Your pantry',
|
|
78
|
+
inputs: ['Bread flour', 'Rye flour', 'Butter', 'Eggs', 'Yeast'],
|
|
79
|
+
selected: ['Bread flour', 'Rye flour', 'Butter', 'Yeast'],
|
|
80
|
+
results: [{ title: 'Seeded rye loaf', meta: 'Uses all 3 · 3 h 20 min', needs: ['Bread flour', 'Rye flour', 'Yeast'] }],
|
|
81
|
+
count: { one: '{n} bake matches', many: '{n} bakes match', none: 'No matches yet' },
|
|
82
|
+
empty: 'Pick two or more ingredients.',
|
|
83
|
+
note: 'Sign in with your email. Your picks come with you.',
|
|
84
|
+
cta: 'Open recipes →',
|
|
85
|
+
lock: 'Full method after sign-in',
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
describe('viable-common - the scaffold plan schema', () => {
|
|
92
|
+
const validate = new Ajv({ strict: false, allErrors: true }).compile(ScaffoldPlanSchema)
|
|
93
|
+
const slot = VIABLE_PROJECT_SLOTS.find(entry => entry.category === ViableSpecCategory.Scaffold)!
|
|
94
|
+
|
|
95
|
+
test('a plan stored before the landing overhaul still validates', () => {
|
|
96
|
+
expect(validate(oldPlan()), JSON.stringify(validate.errors)).toBe(true)
|
|
97
|
+
expect(() => validateSpecificationBody(slot, JSON.stringify(oldPlan()))).not.toThrow()
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test('a plan carrying every new field validates', () => {
|
|
101
|
+
expect(validate(newPlan()), JSON.stringify(validate.errors)).toBe(true)
|
|
102
|
+
expect(() => validateSpecificationBody(slot, JSON.stringify(newPlan()))).not.toThrow()
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
test('a provider that spells an unset optional as null is accepted', () => {
|
|
106
|
+
const plan = newPlan() as unknown as { guestHome: Record<string, unknown> }
|
|
107
|
+
plan.guestHome.gate = null
|
|
108
|
+
plan.guestHome.closing = null
|
|
109
|
+
;(plan.guestHome.hero as Record<string, unknown>).secondary = null
|
|
110
|
+
|
|
111
|
+
expect(validate(plan), JSON.stringify(validate.errors)).toBe(true)
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
test('still refuses an undeclared field and a fragment kind nothing can draw', () => {
|
|
115
|
+
const stray = newPlan() as unknown as { guestHome: Record<string, unknown> }
|
|
116
|
+
stray.guestHome.badge = 'New'
|
|
117
|
+
expect(validate(stray)).toBe(false)
|
|
118
|
+
|
|
119
|
+
const fragment = newPlan()
|
|
120
|
+
fragment.guestHome.features[0].fragment = { kind: 'chart' as never, rows: [] }
|
|
121
|
+
expect(validate(fragment)).toBe(false)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
test('declares no count as minItems/maxItems — counts live in the descriptions', () => {
|
|
125
|
+
const text = JSON.stringify(ScaffoldPlanSchema)
|
|
126
|
+
|
|
127
|
+
expect(text).not.toContain('minItems')
|
|
128
|
+
expect(text).not.toContain('maxItems')
|
|
129
|
+
expect(BENTO_FRAGMENT_KINDS).toEqual(['list', 'note', 'people', 'steps'])
|
|
130
|
+
})
|
|
131
|
+
})
|
package/tests/skills.spec.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { describe, expect, it } from 'bun:test'
|
|
2
2
|
|
|
3
|
-
import { ViableSkill } from '../src/skills/consts.js'
|
|
4
|
-
import { viableSkill } from '../src/skills/catalogue.js'
|
|
3
|
+
import { SKILL_ORDER, ViableSkill } from '../src/skills/consts.js'
|
|
4
|
+
import { VIABLE_SKILLS, viableSkill } from '../src/skills/catalogue.js'
|
|
5
|
+
import { VIABLE_PERSONAS, ViablePersona } from '../src/skills/roles.js'
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Pins the catalogue's own "wrap exactly once" teaching — the incident this guards against was
|
|
@@ -45,3 +46,63 @@ describe('viable-common catalogue — handler wrap-once', () => {
|
|
|
45
46
|
}
|
|
46
47
|
})
|
|
47
48
|
})
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The prompt service SKIPS an alias it cannot resolve, so a skill in the enum with no body is a
|
|
52
|
+
* rule no model ever receives — nothing fails, the prompt simply lacks it.
|
|
53
|
+
*/
|
|
54
|
+
describe('viable-common catalogue — completeness', () => {
|
|
55
|
+
it('declares one body and one sort weight for every skill in the enum', () => {
|
|
56
|
+
const declared = VIABLE_SKILLS.map(entry => entry.alias)
|
|
57
|
+
|
|
58
|
+
expect(Object.values(ViableSkill).filter(alias => !declared.includes(alias))).toEqual([])
|
|
59
|
+
expect(new Set(declared).size).toBe(declared.length)
|
|
60
|
+
for (const alias of Object.values(ViableSkill)) {
|
|
61
|
+
expect(typeof SKILL_ORDER[alias], alias).toBe('number')
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The house style is taught in two places a designer and a coder read — the visual designer's
|
|
68
|
+
* persona and the shadcn skill — and a template ships the same values. Neither may drift back
|
|
69
|
+
* to teaching the decoration the template now paints flat.
|
|
70
|
+
*/
|
|
71
|
+
describe('viable-common catalogue — the house style', () => {
|
|
72
|
+
it('ShadcnUi teaches the white ground, one accent and flat depth, and repaints the old classes', () => {
|
|
73
|
+
const body = viableSkill(ViableSkill.ShadcnUi).body
|
|
74
|
+
|
|
75
|
+
expect(body).toContain('bg-background')
|
|
76
|
+
expect(body).toContain('ONE product accent')
|
|
77
|
+
expect(body).toContain('shadow-floating')
|
|
78
|
+
expect(body).toContain('REPAINTED flat')
|
|
79
|
+
expect(body).toContain('explicitly asks')
|
|
80
|
+
expect(body).not.toContain('DECORATION VOCABULARY')
|
|
81
|
+
expect(body).not.toMatch(/\/\/ right[^\n]*gradient/)
|
|
82
|
+
expect(body).toContain('SelectItem')
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('the visual designer defaults to the house style and changes it only on an explicit ask', () => {
|
|
86
|
+
const role = VIABLE_PERSONAS[ViablePersona.VisualDesigner].role
|
|
87
|
+
|
|
88
|
+
expect(role).toContain('white ground')
|
|
89
|
+
expect(role).toContain('ONE')
|
|
90
|
+
expect(role).toContain('explicitly asks')
|
|
91
|
+
expect(role).not.toContain('expressive and bright')
|
|
92
|
+
expect(role).not.toMatch(/default to .*gradients/)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('LandingGate teaches the gate card, the synchronous handoff and the screen that reads it', () => {
|
|
96
|
+
const body = viableSkill(ViableSkill.LandingGate).body
|
|
97
|
+
|
|
98
|
+
expect(body).toContain('data-home-gate')
|
|
99
|
+
expect(body).toContain('aria-pressed')
|
|
100
|
+
expect(body).toContain('writeLandingHandoff(target')
|
|
101
|
+
expect(body).toContain('readLandingHandoff')
|
|
102
|
+
expect(body).toContain('clearLandingHandoff()')
|
|
103
|
+
expect(body).toContain("useLogin(target)")
|
|
104
|
+
expect(body).toContain('SYNCHRONOUS')
|
|
105
|
+
expect(body).toContain('WRONG')
|
|
106
|
+
expect(body).not.toContain('${')
|
|
107
|
+
})
|
|
108
|
+
})
|
package/build/connect/jobs.d.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import type { ConnectJobKind } from './consts.js';
|
|
2
|
-
/**
|
|
3
|
-
* The separator a job id is joined with.
|
|
4
|
-
*
|
|
5
|
-
* `~`, because every other separator in play is already spoken for: a route alias is
|
|
6
|
-
* colon-delimited and so is every run id (`init:<projectId>`, `story:<storyId>`), so a job id
|
|
7
|
-
* built with one could not be split back apart.
|
|
8
|
-
*/
|
|
9
|
-
export declare const JOB_SEPARATOR = "~";
|
|
10
|
-
/**
|
|
11
|
-
* A job's id: its kind, the project card it belongs to, and the subject it is about.
|
|
12
|
-
*
|
|
13
|
-
* The suffix is a CARD id for a story job and a run id for a pipeline job — never a story code.
|
|
14
|
-
* Declared here rather than by whoever answers the job, because the SDK composes the id it then
|
|
15
|
-
* polls for: two spellings of one id is a job nobody can find.
|
|
16
|
-
*/
|
|
17
|
-
export declare const jobIdOf: (kind: ConnectJobKind, projectId: string, suffix?: string) => string;
|
|
18
|
-
export interface ParsedJobId {
|
|
19
|
-
kind: ConnectJobKind;
|
|
20
|
-
projectId: string;
|
|
21
|
-
suffix?: string;
|
|
22
|
-
}
|
|
23
|
-
/** Split a job id back into what {@link jobIdOf} composed it from. */
|
|
24
|
-
export declare const parseJobId: (jobId: string) => ParsedJobId;
|
|
25
|
-
//# sourceMappingURL=jobs.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"jobs.d.ts","sourceRoot":"","sources":["../../src/connect/jobs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAEjD;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,MAAM,CAAA;AAEhC;;;;;;GAMG;AACH,eAAO,MAAM,OAAO,SAAU,cAAc,aAAa,MAAM,WAAW,MAAM,KAAG,MACP,CAAA;AAE5E,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,cAAc,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,sEAAsE;AACtE,eAAO,MAAM,UAAU,UAAW,MAAM,KAAG,WAW1C,CAAA"}
|
package/build/connect/jobs.js
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The separator a job id is joined with.
|
|
3
|
-
*
|
|
4
|
-
* `~`, because every other separator in play is already spoken for: a route alias is
|
|
5
|
-
* colon-delimited and so is every run id (`init:<projectId>`, `story:<storyId>`), so a job id
|
|
6
|
-
* built with one could not be split back apart.
|
|
7
|
-
*/
|
|
8
|
-
export const JOB_SEPARATOR = '~';
|
|
9
|
-
/**
|
|
10
|
-
* A job's id: its kind, the project card it belongs to, and the subject it is about.
|
|
11
|
-
*
|
|
12
|
-
* The suffix is a CARD id for a story job and a run id for a pipeline job — never a story code.
|
|
13
|
-
* Declared here rather than by whoever answers the job, because the SDK composes the id it then
|
|
14
|
-
* polls for: two spellings of one id is a job nobody can find.
|
|
15
|
-
*/
|
|
16
|
-
export const jobIdOf = (kind, projectId, suffix) => [kind, projectId, ...(suffix != null ? [suffix] : [])].join(JOB_SEPARATOR);
|
|
17
|
-
/** Split a job id back into what {@link jobIdOf} composed it from. */
|
|
18
|
-
export const parseJobId = (jobId) => {
|
|
19
|
-
const [kind, projectId, ...rest] = jobId.split(JOB_SEPARATOR);
|
|
20
|
-
// A suffix may carry the separator back if one ever ends up in a card or run id, so the tail is
|
|
21
|
-
// rejoined rather than taken as its first segment.
|
|
22
|
-
const suffix = rest.length > 0 ? rest.join(JOB_SEPARATOR) : undefined;
|
|
23
|
-
return {
|
|
24
|
-
kind: kind,
|
|
25
|
-
projectId: projectId ?? '',
|
|
26
|
-
...(suffix != null ? { suffix } : {}),
|
|
27
|
-
};
|
|
28
|
-
};
|
|
29
|
-
//# sourceMappingURL=jobs.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"jobs.js","sourceRoot":"","sources":["../../src/connect/jobs.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,GAAG,CAAA;AAEhC;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,IAAoB,EAAE,SAAiB,EAAE,MAAe,EAAU,EAAE,CAC1F,CAAC,IAAI,EAAE,SAAS,EAAE,GAAG,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAA;AAQ5E,sEAAsE;AACtE,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,KAAa,EAAe,EAAE;IACvD,MAAM,CAAC,IAAI,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,aAAa,CAAC,CAAA;IAC7D,gGAAgG;IAChG,mDAAmD;IACnD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IAErE,OAAO;QACL,IAAI,EAAE,IAAsB;QAC5B,SAAS,EAAE,SAAS,IAAI,EAAE;QAC1B,GAAG,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACtC,CAAA;AACH,CAAC,CAAA"}
|
package/src/connect/jobs.ts
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import type { ConnectJobKind } from './consts.js'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* The separator a job id is joined with.
|
|
5
|
-
*
|
|
6
|
-
* `~`, because every other separator in play is already spoken for: a route alias is
|
|
7
|
-
* colon-delimited and so is every run id (`init:<projectId>`, `story:<storyId>`), so a job id
|
|
8
|
-
* built with one could not be split back apart.
|
|
9
|
-
*/
|
|
10
|
-
export const JOB_SEPARATOR = '~'
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* A job's id: its kind, the project card it belongs to, and the subject it is about.
|
|
14
|
-
*
|
|
15
|
-
* The suffix is a CARD id for a story job and a run id for a pipeline job — never a story code.
|
|
16
|
-
* Declared here rather than by whoever answers the job, because the SDK composes the id it then
|
|
17
|
-
* polls for: two spellings of one id is a job nobody can find.
|
|
18
|
-
*/
|
|
19
|
-
export const jobIdOf = (kind: ConnectJobKind, projectId: string, suffix?: string): string =>
|
|
20
|
-
[kind, projectId, ...(suffix != null ? [suffix] : [])].join(JOB_SEPARATOR)
|
|
21
|
-
|
|
22
|
-
export interface ParsedJobId {
|
|
23
|
-
kind: ConnectJobKind
|
|
24
|
-
projectId: string
|
|
25
|
-
suffix?: string
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/** Split a job id back into what {@link jobIdOf} composed it from. */
|
|
29
|
-
export const parseJobId = (jobId: string): ParsedJobId => {
|
|
30
|
-
const [kind, projectId, ...rest] = jobId.split(JOB_SEPARATOR)
|
|
31
|
-
// A suffix may carry the separator back if one ever ends up in a card or run id, so the tail is
|
|
32
|
-
// rejoined rather than taken as its first segment.
|
|
33
|
-
const suffix = rest.length > 0 ? rest.join(JOB_SEPARATOR) : undefined
|
|
34
|
-
|
|
35
|
-
return {
|
|
36
|
-
kind: kind as ConnectJobKind,
|
|
37
|
-
projectId: projectId ?? '',
|
|
38
|
-
...(suffix != null ? { suffix } : {}),
|
|
39
|
-
}
|
|
40
|
-
}
|