@meistrari/tela-build 1.62.1 → 1.64.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/components/tela/modal/switch-workspace/switch-workspace-content.vue +51 -31
- package/components/tela/modal/switch-workspace/switch-workspace.vue +2 -0
- package/lib/__tests__/doc-generator.test.ts +27 -0
- package/lib/doc-generator.ts +64 -3
- package/lib/tela-build-skill.ts +41 -0
- package/package.json +1 -1
|
@@ -9,6 +9,7 @@ const props = defineProps<{
|
|
|
9
9
|
projectLabel: string
|
|
10
10
|
projectsLabel: string
|
|
11
11
|
selectedLabel: string
|
|
12
|
+
loading?: boolean
|
|
12
13
|
}>()
|
|
13
14
|
|
|
14
15
|
const emit = defineEmits<{
|
|
@@ -24,39 +25,58 @@ function formatStat(count: number | undefined, singular: string, plural: string)
|
|
|
24
25
|
</script>
|
|
25
26
|
|
|
26
27
|
<template>
|
|
27
|
-
<
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
data-testid="tela.switchWorkspace.workspaceButton"
|
|
37
|
-
@click="emit('select', workspace)"
|
|
38
|
-
>
|
|
39
|
-
<div mr-12px :class="activeWorkspaceId === workspace.id && 'ring-1.5px ring-offset-2px ring-gray-400 rounded-10px'">
|
|
40
|
-
<TelaAvatar size="md" :image="workspace.logo ?? undefined" :alt="workspace.name" class="!rounded-[10px]" />
|
|
41
|
-
</div>
|
|
28
|
+
<template v-if="loading">
|
|
29
|
+
<div
|
|
30
|
+
v-for="index in 3"
|
|
31
|
+
:key="index"
|
|
32
|
+
h-64px
|
|
33
|
+
flex w-full items-center px-16px
|
|
34
|
+
not-last:border-b="0.5px border"
|
|
35
|
+
>
|
|
36
|
+
<TelaSkeleton mr-12px h-40px w-40px rounded-10px />
|
|
42
37
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
</h5>
|
|
47
|
-
<div flex items-center gap-4px>
|
|
48
|
-
<p body-12-regular text-tertiary>
|
|
49
|
-
{{ formatStat(workspace.stats?.memberCount, props.memberLabel, props.membersLabel) }}
|
|
50
|
-
</p>
|
|
51
|
-
<div aria-hidden="true" w-3px h-3px rounded-full bg-gray-200 />
|
|
52
|
-
<p body-12-regular text-tertiary>
|
|
53
|
-
{{ formatStat(workspace.stats?.projectCount, props.projectLabel, props.projectsLabel) }}
|
|
54
|
-
</p>
|
|
38
|
+
<div flex="~ col" gap-8px>
|
|
39
|
+
<TelaSkeleton h-14px w-150px rounded-4px />
|
|
40
|
+
<TelaSkeleton h-12px w-96px rounded-4px />
|
|
55
41
|
</div>
|
|
56
42
|
</div>
|
|
43
|
+
</template>
|
|
44
|
+
|
|
45
|
+
<template v-else>
|
|
46
|
+
<button
|
|
47
|
+
v-for="workspace of workspaces"
|
|
48
|
+
:key="workspace.id"
|
|
49
|
+
h-64px
|
|
50
|
+
flex w-full items-center px-16px
|
|
51
|
+
hover:bg-muted
|
|
52
|
+
first:rounded-t-12px
|
|
53
|
+
last:rounded-b-12px
|
|
54
|
+
not-last:border-b="0.5px border"
|
|
55
|
+
data-testid="tela.switchWorkspace.workspaceButton"
|
|
56
|
+
@click="emit('select', workspace)"
|
|
57
|
+
>
|
|
58
|
+
<div mr-12px :class="activeWorkspaceId === workspace.id && 'ring-1.5px ring-offset-2px ring-gray-400 rounded-10px'">
|
|
59
|
+
<TelaAvatar size="md" :image="workspace.logo ?? undefined" :alt="workspace.name" class="!rounded-[10px]" />
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<div flex="~ col" gap-2px>
|
|
63
|
+
<h5 heading-h5-semibold text-primary text-start>
|
|
64
|
+
{{ workspace.name }}
|
|
65
|
+
</h5>
|
|
66
|
+
<div flex items-center gap-4px>
|
|
67
|
+
<p body-12-regular text-tertiary>
|
|
68
|
+
{{ formatStat(workspace.stats?.memberCount, props.memberLabel, props.membersLabel) }}
|
|
69
|
+
</p>
|
|
70
|
+
<div aria-hidden="true" w-3px h-3px rounded-full bg-gray-200 />
|
|
71
|
+
<p body-12-regular text-tertiary>
|
|
72
|
+
{{ formatStat(workspace.stats?.projectCount, props.projectLabel, props.projectsLabel) }}
|
|
73
|
+
</p>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
57
76
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
77
|
+
<TelaBadge v-if="activeWorkspaceId === workspace.id" ml-auto>
|
|
78
|
+
{{ props.selectedLabel }}
|
|
79
|
+
</TelaBadge>
|
|
80
|
+
</button>
|
|
81
|
+
</template>
|
|
62
82
|
</template>
|
|
@@ -11,6 +11,7 @@ defineProps<{
|
|
|
11
11
|
projectLabel: string
|
|
12
12
|
projectsLabel: string
|
|
13
13
|
selectedLabel: string
|
|
14
|
+
loading?: boolean
|
|
14
15
|
}>()
|
|
15
16
|
|
|
16
17
|
const emit = defineEmits<{
|
|
@@ -60,6 +61,7 @@ function onClose() {
|
|
|
60
61
|
<div overflow-y-auto no-scrollbar>
|
|
61
62
|
<TelaModalSwitchWorkspaceContent
|
|
62
63
|
:workspaces="workspaces"
|
|
64
|
+
:loading="loading"
|
|
63
65
|
:active-workspace-id="activeWorkspaceId"
|
|
64
66
|
:member-label="memberLabel"
|
|
65
67
|
:members-label="membersLabel"
|
|
@@ -34,6 +34,33 @@ describe('generateDocsToDirectory', () => {
|
|
|
34
34
|
expect(skillMd).toContain('description: "')
|
|
35
35
|
})
|
|
36
36
|
|
|
37
|
+
// The pantry block was originally hand-written into the generated SKILL.md and
|
|
38
|
+
// would have been deleted by the next generation run. It has to come from the
|
|
39
|
+
// generator, and the values have to survive: Pantry derives shelf slugs from
|
|
40
|
+
// these strings, so lowercasing one renames a shelf shared across the catalog.
|
|
41
|
+
it('emits the pantry block for the tela-build skill', () => {
|
|
42
|
+
const outDir = mkdtempSync(join(tmpdir(), 'tela-build-docs-'))
|
|
43
|
+
tempDirs.push(outDir)
|
|
44
|
+
|
|
45
|
+
generateDocsToDirectory([], new TypeResolver(outDir), outDir)
|
|
46
|
+
|
|
47
|
+
const parsed = matter(readFileSync(join(outDir, 'tela-build', 'SKILL.md'), 'utf-8'))
|
|
48
|
+
|
|
49
|
+
expect(parsed.data.pantry).toEqual({
|
|
50
|
+
'owner': ['guhrodrrigues'],
|
|
51
|
+
'status': 'stable',
|
|
52
|
+
'shelves': ['Frontend', 'Design Engineering', 'Developer Tools'],
|
|
53
|
+
'use-cases': [
|
|
54
|
+
'Look up a Tela Build component\'s props, slots, variants, and usage examples before writing UI code',
|
|
55
|
+
'Pick the correct design token for a color, text style, border, shadow, or spacing decision',
|
|
56
|
+
'Polish an existing interface\'s radius, elevation, optical alignment, typography, and animations',
|
|
57
|
+
'Review frontend changes for component reuse and design-system compliance',
|
|
58
|
+
],
|
|
59
|
+
'bundle': 'tela-build',
|
|
60
|
+
'artifacts': ['components'],
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
|
|
37
64
|
it('appends package docs to the tela-build skill', () => {
|
|
38
65
|
const outDir = mkdtempSync(join(tmpdir(), 'tela-build-docs-'))
|
|
39
66
|
const layerPath = mkdtempSync(join(tmpdir(), 'tela-build-layer-'))
|
package/lib/doc-generator.ts
CHANGED
|
@@ -7,7 +7,8 @@ import { getTypeResolver } from './type-resolver'
|
|
|
7
7
|
import type { TypeResolver } from './type-resolver'
|
|
8
8
|
import { createVolarExtractor } from './extractors/volar-extract'
|
|
9
9
|
import type { VolarExtractor } from './extractors/volar-extract'
|
|
10
|
-
import { telaBuildSkill } from './tela-build-skill'
|
|
10
|
+
import { telaBuildSkill, telaBuildSkillPantry } from './tela-build-skill'
|
|
11
|
+
import type { SkillPantryMetadata } from './tela-build-skill'
|
|
11
12
|
|
|
12
13
|
const colors = {
|
|
13
14
|
gray: '\x1B[90m',
|
|
@@ -783,6 +784,7 @@ ${componentLinks.join('\n')}${docsSection}
|
|
|
783
784
|
const skillMd = wrapWithSkillFrontmatter({
|
|
784
785
|
name: telaBuildSkill.name,
|
|
785
786
|
description: telaBuildSkill.description,
|
|
787
|
+
pantry: telaBuildSkillPantry,
|
|
786
788
|
}, body)
|
|
787
789
|
writeFileSync(join(skillDir, 'SKILL.md'), skillMd, 'utf-8')
|
|
788
790
|
}
|
|
@@ -887,8 +889,8 @@ function toKebabFromTag(tagName: string): string {
|
|
|
887
889
|
.replace(/^-+|-+$/g, '')
|
|
888
890
|
}
|
|
889
891
|
|
|
890
|
-
function wrapWithSkillFrontmatter(meta: { name: string, description: string, allowedTools?: string[] }, body: string): string {
|
|
891
|
-
const { name, description, allowedTools } = meta
|
|
892
|
+
function wrapWithSkillFrontmatter(meta: { name: string, description: string, allowedTools?: string[], pantry?: SkillPantryMetadata }, body: string): string {
|
|
893
|
+
const { name, description, allowedTools, pantry } = meta
|
|
892
894
|
const lines: string[] = []
|
|
893
895
|
lines.push('---')
|
|
894
896
|
lines.push(`name: ${sanitizeSkillName(name)}`)
|
|
@@ -896,6 +898,9 @@ function wrapWithSkillFrontmatter(meta: { name: string, description: string, all
|
|
|
896
898
|
if (allowedTools && allowedTools.length > 0) {
|
|
897
899
|
lines.push(`allowed-tools: ${allowedTools.join(', ')}`)
|
|
898
900
|
}
|
|
901
|
+
if (pantry) {
|
|
902
|
+
lines.push(...renderPantryFrontmatter(pantry))
|
|
903
|
+
}
|
|
899
904
|
lines.push('---')
|
|
900
905
|
lines.push('')
|
|
901
906
|
lines.push(body.trimStart())
|
|
@@ -918,6 +923,62 @@ function quoteFrontmatterString(value: string): string {
|
|
|
918
923
|
return JSON.stringify(value)
|
|
919
924
|
}
|
|
920
925
|
|
|
926
|
+
/**
|
|
927
|
+
* A YAML plain scalar, unless the value could be read as anything other than the
|
|
928
|
+
* string it is — then a double-quoted scalar. Quoting only what needs it keeps
|
|
929
|
+
* the generated frontmatter readable and stable.
|
|
930
|
+
*/
|
|
931
|
+
function formatFrontmatterScalar(value: string): string {
|
|
932
|
+
return isPlainFrontmatterScalar(value) ? value : quoteFrontmatterString(value)
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
function isPlainFrontmatterScalar(value: string): boolean {
|
|
936
|
+
// Surrounding whitespace is not preserved by a plain scalar.
|
|
937
|
+
if (!value || value !== value.trim())
|
|
938
|
+
return false
|
|
939
|
+
// A leading indicator character starts a collection, comment, anchor, tag, or
|
|
940
|
+
// quoted/block scalar rather than a plain one.
|
|
941
|
+
if (/^[-?:,[\]{}#&*!|>'"%@`]/.test(value))
|
|
942
|
+
return false
|
|
943
|
+
// ": " opens a mapping, " #" opens a comment, and a trailing ":" makes a key.
|
|
944
|
+
if (value.includes(': ') || value.includes(' #') || value.endsWith(':'))
|
|
945
|
+
return false
|
|
946
|
+
if (/[\n\r\t]/.test(value))
|
|
947
|
+
return false
|
|
948
|
+
// These would round-trip as a boolean, null, or number instead of a string.
|
|
949
|
+
if (/^(?:true|false|null|yes|no|on|off|~)$/i.test(value))
|
|
950
|
+
return false
|
|
951
|
+
return Number.isNaN(Number(value))
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
/**
|
|
955
|
+
* Render the `pantry:` block. The key order is fixed rather than derived from
|
|
956
|
+
* object iteration so the output is byte-stable across regenerations.
|
|
957
|
+
*/
|
|
958
|
+
function renderPantryFrontmatter(pantry: SkillPantryMetadata): string[] {
|
|
959
|
+
const lines: string[] = ['pantry:']
|
|
960
|
+
|
|
961
|
+
// An empty sequence would emit a bare key that parses as null, so omit it and
|
|
962
|
+
// let the consumer report a missing field instead of a malformed one.
|
|
963
|
+
const pushSequence = (key: string, values: readonly string[]): void => {
|
|
964
|
+
if (values.length === 0)
|
|
965
|
+
return
|
|
966
|
+
lines.push(` ${key}:`)
|
|
967
|
+
for (const value of values) {
|
|
968
|
+
lines.push(` - ${formatFrontmatterScalar(value)}`)
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
pushSequence('owner', pantry.owner)
|
|
973
|
+
lines.push(` status: ${formatFrontmatterScalar(pantry.status)}`)
|
|
974
|
+
pushSequence('shelves', pantry.shelves)
|
|
975
|
+
pushSequence('use-cases', pantry.useCases)
|
|
976
|
+
lines.push(` bundle: ${formatFrontmatterScalar(pantry.bundle)}`)
|
|
977
|
+
pushSequence('artifacts', pantry.artifacts)
|
|
978
|
+
|
|
979
|
+
return lines
|
|
980
|
+
}
|
|
981
|
+
|
|
921
982
|
function toGroupSlugFromKebab(kebab: string): string {
|
|
922
983
|
const parts = kebab.split('-').filter(Boolean)
|
|
923
984
|
if (parts.length <= 2)
|
package/lib/tela-build-skill.ts
CHANGED
|
@@ -1,4 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pantry metadata for a generated skill, emitted as a nested `pantry:` block in
|
|
3
|
+
* the SKILL.md frontmatter.
|
|
4
|
+
*
|
|
5
|
+
* This is plain data on purpose: nothing here imports from Pantry or talks to it
|
|
6
|
+
* over the network. Generation stays entirely local, and a generator run that
|
|
7
|
+
* supplies no metadata produces exactly the frontmatter it always did.
|
|
8
|
+
*/
|
|
9
|
+
export interface SkillPantryMetadata {
|
|
10
|
+
owner: readonly string[]
|
|
11
|
+
status: string
|
|
12
|
+
shelves: readonly string[]
|
|
13
|
+
useCases: readonly string[]
|
|
14
|
+
bundle: string
|
|
15
|
+
artifacts: readonly string[]
|
|
16
|
+
}
|
|
17
|
+
|
|
1
18
|
export const telaBuildSkill = {
|
|
2
19
|
name: 'tela-build',
|
|
3
20
|
description: 'Use when working on any UI in this repository. Ask for component props, slots, variants, and usage examples. Always consult this Tela Build index BEFORE making any UI changes or creating new UI. Use it for components, layouts, styling, interactions, and design-token decisions.',
|
|
4
21
|
} as const
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Pantry catalog entry for the tela-build skill. It lives here rather than in
|
|
25
|
+
* `.agents/skills/tela-build/SKILL.md` because that file is generated: a block
|
|
26
|
+
* hand-written into the output is deleted by the next `generate-docs` run (and
|
|
27
|
+
* by every dev-server start, which regenerates through the Nuxt module).
|
|
28
|
+
*
|
|
29
|
+
* Shelf names are Title Case deliberately. Pantry derives shelf slugs from these
|
|
30
|
+
* strings, so lowercasing them would rename shelves shared with other
|
|
31
|
+
* ingredients across the whole catalog.
|
|
32
|
+
*/
|
|
33
|
+
export const telaBuildSkillPantry = {
|
|
34
|
+
owner: ['guhrodrrigues'],
|
|
35
|
+
status: 'stable',
|
|
36
|
+
shelves: ['Frontend', 'Design Engineering', 'Developer Tools'],
|
|
37
|
+
useCases: [
|
|
38
|
+
'Look up a Tela Build component\'s props, slots, variants, and usage examples before writing UI code',
|
|
39
|
+
'Pick the correct design token for a color, text style, border, shadow, or spacing decision',
|
|
40
|
+
'Polish an existing interface\'s radius, elevation, optical alignment, typography, and animations',
|
|
41
|
+
'Review frontend changes for component reuse and design-system compliance',
|
|
42
|
+
],
|
|
43
|
+
bundle: 'tela-build',
|
|
44
|
+
artifacts: ['components'],
|
|
45
|
+
} as const satisfies SkillPantryMetadata
|