agent-facets 0.2.2 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/bin/facet +181 -0
  2. package/bin/package.json +3 -0
  3. package/package.json +17 -37
  4. package/postinstall.mjs +210 -0
  5. package/.package.json.bak +0 -44
  6. package/.turbo/turbo-build.log +0 -3
  7. package/CHANGELOG.md +0 -85
  8. package/bunfig.toml +0 -2
  9. package/dist/facet +0 -0
  10. package/src/__tests__/cli.test.ts +0 -195
  11. package/src/__tests__/create-build.test.ts +0 -227
  12. package/src/__tests__/edit-integration.test.ts +0 -171
  13. package/src/__tests__/resolve-dir.test.ts +0 -95
  14. package/src/commands/build.ts +0 -58
  15. package/src/commands/create/index.ts +0 -76
  16. package/src/commands/create/types.ts +0 -9
  17. package/src/commands/create/wizard.tsx +0 -75
  18. package/src/commands/create-scaffold.ts +0 -184
  19. package/src/commands/edit/index.ts +0 -144
  20. package/src/commands/edit/wizard.tsx +0 -74
  21. package/src/commands/resolve-dir.ts +0 -98
  22. package/src/commands.ts +0 -40
  23. package/src/help.ts +0 -43
  24. package/src/index.ts +0 -10
  25. package/src/run.ts +0 -82
  26. package/src/suggest.ts +0 -35
  27. package/src/tui/components/asset-description.tsx +0 -17
  28. package/src/tui/components/asset-field-picker.tsx +0 -78
  29. package/src/tui/components/asset-inline-input.tsx +0 -91
  30. package/src/tui/components/asset-item.tsx +0 -44
  31. package/src/tui/components/asset-section.tsx +0 -191
  32. package/src/tui/components/button.tsx +0 -92
  33. package/src/tui/components/editable-field.tsx +0 -172
  34. package/src/tui/components/exit-toast.tsx +0 -20
  35. package/src/tui/components/reconciliation-item.tsx +0 -129
  36. package/src/tui/components/stage-row.tsx +0 -45
  37. package/src/tui/components/version-selector.tsx +0 -79
  38. package/src/tui/context/focus-mode-context.ts +0 -36
  39. package/src/tui/context/focus-order-context.ts +0 -68
  40. package/src/tui/context/form-state-context.ts +0 -260
  41. package/src/tui/editor.ts +0 -40
  42. package/src/tui/gradient.ts +0 -1
  43. package/src/tui/hooks/use-exit-keys.ts +0 -75
  44. package/src/tui/hooks/use-navigation-keys.ts +0 -34
  45. package/src/tui/layouts/wizard-layout.tsx +0 -41
  46. package/src/tui/theme.ts +0 -1
  47. package/src/tui/views/build/build-view.tsx +0 -152
  48. package/src/tui/views/create/confirm-view.tsx +0 -74
  49. package/src/tui/views/create/create-view.tsx +0 -158
  50. package/src/tui/views/create/wizard.tsx +0 -97
  51. package/src/tui/views/edit/edit-confirm-view.tsx +0 -93
  52. package/src/tui/views/edit/edit-types.ts +0 -34
  53. package/src/tui/views/edit/edit-view.tsx +0 -140
  54. package/src/tui/views/edit/manifest-to-form.ts +0 -38
  55. package/src/tui/views/edit/reconciliation-view.tsx +0 -170
  56. package/src/tui/views/edit/use-edit-session.ts +0 -125
  57. package/src/tui/views/edit/wizard.tsx +0 -129
  58. package/src/version.ts +0 -3
  59. package/tsconfig.json +0 -4
@@ -1,58 +0,0 @@
1
- import { render } from 'ink'
2
- import { createElement } from 'react'
3
- import type { Command } from '../commands.ts'
4
- import { BuildView } from '../tui/views/build/build-view.tsx'
5
- import { resolveTargetDir } from './resolve-dir.ts'
6
-
7
- export const buildCommand: Command = {
8
- name: 'build',
9
- description: 'Build a facet from the current directory',
10
- usage: '[directory]',
11
- run: async (args: string[], _flags: Record<string, unknown>): Promise<number> => {
12
- const resolved = await resolveTargetDir(args[0], { mustExist: true, facetMustExist: true })
13
- if (!resolved.ok) {
14
- console.error(resolved.message)
15
- return 1
16
- }
17
-
18
- const rootDir = resolved.dir
19
- const displayDir = resolved.display
20
-
21
- // Track result for stdout summary after Ink exits
22
- let buildName = ''
23
- let buildVersion = ''
24
- let artifactCount = 0
25
- let integrity = ''
26
- let errorCount = 0
27
-
28
- const instance = render(
29
- createElement(BuildView, {
30
- rootDir,
31
- onSuccess: (name: string, version: string, fileCount: number, hash: string) => {
32
- buildName = name
33
- buildVersion = version
34
- artifactCount = fileCount
35
- integrity = hash
36
- },
37
- onFailure: (count: number) => {
38
- errorCount = count
39
- },
40
- }),
41
- )
42
-
43
- try {
44
- await instance.waitUntilExit()
45
- // Ink has unmounted — print stdout summary for scroll-back
46
- const shortHash = integrity.length > 20 ? `${integrity.slice(0, 20)}...` : integrity
47
- process.stdout.write(
48
- `✓ Built ${buildName} v${buildVersion} → ${displayDir}/dist/ (${artifactCount} assets, ${shortHash})\n`,
49
- )
50
- return 0
51
- } catch {
52
- process.stdout.write(
53
- `✗ Build failed — ${errorCount} error${errorCount !== 1 ? 's' : ''}. Run \`facet edit${args[0] ? ` ${displayDir}` : ''}\` to fix.\n`,
54
- )
55
- return 1
56
- }
57
- },
58
- }
@@ -1,76 +0,0 @@
1
- import { join } from 'node:path'
2
- import { createInterface } from 'node:readline'
3
- import { FACET_MANIFEST_FILE } from '@agent-facets/core'
4
- import { type } from 'arktype'
5
- import type { Command } from '../../commands.ts'
6
- import { writeScaffold } from '../create-scaffold.ts'
7
- import { resolveTargetDir } from '../resolve-dir.ts'
8
- import { runCreateWizardInk } from './wizard.tsx'
9
-
10
- export type { CreateOptions } from '../create-scaffold.ts'
11
- export { writeScaffold } from '../create-scaffold.ts'
12
-
13
- const CreateFlags = type({ 'force?': 'boolean' })
14
-
15
- async function confirmOverwrite(display: string): Promise<boolean> {
16
- const rl = createInterface({ input: process.stdin, output: process.stdout })
17
-
18
- return new Promise((resolve) => {
19
- rl.question(`A facet already exists in ${display}. Overwrite? (y/N) `, (answer) => {
20
- rl.close()
21
- resolve(answer.toLowerCase() === 'y')
22
- })
23
- })
24
- }
25
-
26
- export const createCommand: Command = {
27
- name: 'create',
28
- description: 'Create a new facet project interactively',
29
- usage: '[directory]',
30
- flags: {
31
- force: { type: 'boolean', description: 'Overwrite existing facet.json' },
32
- },
33
- run: async (args: string[], flags: Record<string, unknown>): Promise<number> => {
34
- const resolved = await resolveTargetDir(args[0], { mustExist: false })
35
- if (!resolved.ok) {
36
- console.error(resolved.message)
37
- return 1
38
- }
39
-
40
- const targetDir = resolved.dir
41
- const displayDir = resolved.display
42
-
43
- // Validate flags via Arktype
44
- const validatedFlags = CreateFlags(flags)
45
- if (validatedFlags instanceof type.errors) {
46
- console.error(`Invalid flags: ${validatedFlags.summary}`)
47
- return 1
48
- }
49
-
50
- // Overwrite protection
51
- const manifestExists = await Bun.file(join(targetDir, FACET_MANIFEST_FILE)).exists()
52
- if (manifestExists && !validatedFlags.force) {
53
- const confirmed = await confirmOverwrite(displayDir)
54
- if (!confirmed) {
55
- console.log('Cancelled.')
56
- return 1
57
- }
58
- }
59
-
60
- const opts = await runCreateWizardInk()
61
- if (!opts) {
62
- console.log('\nCancelled.')
63
- return 1
64
- }
65
-
66
- const files = await writeScaffold(opts, targetDir)
67
-
68
- console.log(`\nFacet created: ${opts.name} → ${displayDir}`)
69
- for (const file of files) {
70
- console.log(` ${displayDir}/${file}`)
71
- }
72
- console.log(`\nRun "facet build${args[0] ? ` ${displayDir}` : ''}" to validate your facet.`)
73
-
74
- return 0
75
- },
76
- }
@@ -1,9 +0,0 @@
1
- export type AssetType = 'skill' | 'agent' | 'command'
2
-
3
- export const ASSET_TYPES: AssetType[] = ['skill', 'command', 'agent']
4
-
5
- export const ASSET_LABELS: Record<AssetType, string> = {
6
- skill: 'Skills',
7
- agent: 'Agents',
8
- command: 'Commands',
9
- }
@@ -1,75 +0,0 @@
1
- import { render } from 'ink'
2
- import type { AssetSectionKey } from '../../tui/context/form-state-context.ts'
3
- import { openInEditorSync } from '../../tui/editor.ts'
4
- import type { WizardSnapshot } from '../../tui/views/create/wizard.tsx'
5
- import { CreateWizard } from '../../tui/views/create/wizard.tsx'
6
- import type { CreateOptions } from '../create-scaffold.ts'
7
-
8
- interface EditorRequest {
9
- section: AssetSectionKey
10
- name: string
11
- description: string
12
- }
13
-
14
- export async function runCreateWizardInk(): Promise<CreateOptions | null> {
15
- let result: CreateOptions | null = null
16
- let snapshot: WizardSnapshot | undefined
17
- let pendingEditor: EditorRequest | null = null
18
- let done = false
19
-
20
- while (!done) {
21
- pendingEditor = null
22
-
23
- await new Promise<void>((resolve) => {
24
- const instance = render(
25
- <CreateWizard
26
- snapshot={snapshot}
27
- onComplete={(opts) => {
28
- result = opts
29
- }}
30
- onCancel={() => {
31
- result = null
32
- }}
33
- onSnapshot={(s) => {
34
- snapshot = s
35
- }}
36
- onRequestEditor={(section, name, description) => {
37
- pendingEditor = { section, name, description }
38
- instance.unmount()
39
- }}
40
- />,
41
- )
42
-
43
- instance.waitUntilExit().then(() => resolve())
44
- })
45
-
46
- if (pendingEditor) {
47
- const req = pendingEditor as EditorRequest
48
- const edited = openInEditorSync(req.description, `${req.name}.md`)
49
- if (snapshot) {
50
- const section = snapshot.form.assets[req.section]
51
- snapshot = {
52
- ...snapshot,
53
- selectedItem: undefined,
54
- form: {
55
- ...snapshot.form,
56
- assets: {
57
- ...snapshot.form.assets,
58
- [req.section]: {
59
- ...section,
60
- descriptions: {
61
- ...section.descriptions,
62
- ...(edited !== null ? { [req.name]: edited.trim() } : {}),
63
- },
64
- },
65
- },
66
- },
67
- }
68
- }
69
- } else {
70
- done = true
71
- }
72
- }
73
-
74
- return result
75
- }
@@ -1,184 +0,0 @@
1
- import { mkdir } from 'node:fs/promises'
2
- import { join } from 'node:path'
3
- import { FACET_MANIFEST_FILE, KEBAB_CASE } from '@agent-facets/core'
4
-
5
- // --- Types ---
6
-
7
- export interface CreateOptions {
8
- name: string
9
- version: string
10
- description: string
11
- skills: string[]
12
- agents: string[]
13
- commands: string[]
14
- }
15
-
16
- // --- Defaults ---
17
-
18
- export const DEFAULT_VERSION = '0.0.0'
19
-
20
- // --- Validation ---
21
-
22
- export { KEBAB_CASE }
23
- export const SEMVER = /^\d+\.\d+\.\d+$/
24
-
25
- export function isValidKebabCase(value: string): boolean {
26
- return KEBAB_CASE.test(value)
27
- }
28
-
29
- export function isValidSemVer(value: string): boolean {
30
- return SEMVER.test(value)
31
- }
32
-
33
- // --- Template generation ---
34
-
35
- function toTitleCase(kebab: string): string {
36
- return kebab
37
- .split('-')
38
- .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
39
- .join(' ')
40
- }
41
-
42
- export function skillTemplate(name: string): string {
43
- return `# ${toTitleCase(name)}
44
-
45
- <!-- This is a starter skill template. Replace this content with your skill's instructions. -->
46
- <!-- Skills provide reusable knowledge and guidelines that agents and commands can reference. -->
47
- <!-- A skill needs a description (required) — the description helps consumers decide -->
48
- <!-- whether to use this skill. The prompt content is this file. -->
49
-
50
- ## Purpose
51
-
52
- Describe what this skill teaches or what guidelines it provides.
53
-
54
- ## Guidelines
55
-
56
- - Add your skill's guidelines here
57
- - Each guideline should be clear and actionable
58
- `
59
- }
60
-
61
- export function agentTemplate(name: string): string {
62
- return `# ${toTitleCase(name)}
63
-
64
- <!-- This is a starter agent template. Replace this content with your agent's prompt. -->
65
- <!-- Agents are AI assistant personas with specific roles, behaviors, and tool access. -->
66
-
67
- ## Role
68
-
69
- Describe this agent's role and responsibilities.
70
-
71
- ## Behavior
72
-
73
- - Define how this agent should behave
74
- - Specify what tools it should use
75
- - Describe its communication style
76
- `
77
- }
78
-
79
- export function commandTemplate(name: string): string {
80
- return `# ${toTitleCase(name)}
81
-
82
- <!-- This is a starter command template. Replace this content with your command's prompt. -->
83
- <!-- Commands are user-invokable actions that perform specific tasks. -->
84
-
85
- ## Task
86
-
87
- Describe what this command does when invoked.
88
-
89
- ## Steps
90
-
91
- 1. First step
92
- 2. Second step
93
- 3. Final step
94
- `
95
- }
96
-
97
- // --- Manifest generation ---
98
-
99
- export function generateManifest(opts: CreateOptions): string {
100
- const manifest: Record<string, unknown> = {
101
- name: opts.name,
102
- version: opts.version,
103
- }
104
-
105
- if (opts.description) {
106
- manifest.description = opts.description
107
- }
108
-
109
- if (opts.skills.length > 0) {
110
- const skills: Record<string, { description: string }> = {}
111
- for (const skill of opts.skills) {
112
- skills[skill] = { description: `A ${toTitleCase(skill)} skill` }
113
- }
114
- manifest.skills = skills
115
- }
116
-
117
- if (opts.agents.length > 0) {
118
- const agents: Record<string, { description: string }> = {}
119
- for (const agent of opts.agents) {
120
- agents[agent] = { description: `A ${toTitleCase(agent)} agent` }
121
- }
122
- manifest.agents = agents
123
- }
124
-
125
- if (opts.commands.length > 0) {
126
- const commands: Record<string, { description: string }> = {}
127
- for (const command of opts.commands) {
128
- commands[command] = { description: `A ${toTitleCase(command)} command` }
129
- }
130
- manifest.commands = commands
131
- }
132
-
133
- return JSON.stringify(manifest, null, 2)
134
- }
135
-
136
- // --- File listing preview ---
137
-
138
- export function previewFiles(opts: CreateOptions): string[] {
139
- const files: string[] = [FACET_MANIFEST_FILE]
140
- for (const skill of opts.skills) {
141
- files.push(`skills/${skill}/SKILL.md`)
142
- }
143
- for (const agent of opts.agents) {
144
- files.push(`agents/${agent}.md`)
145
- }
146
- for (const command of opts.commands) {
147
- files.push(`commands/${command}.md`)
148
- }
149
- return files
150
- }
151
-
152
- // --- Scaffold writing ---
153
-
154
- export async function writeScaffold(opts: CreateOptions, targetDir: string): Promise<string[]> {
155
- const files: string[] = []
156
-
157
- // Write manifest
158
- const manifestPath = join(targetDir, FACET_MANIFEST_FILE)
159
- await Bun.write(manifestPath, generateManifest(opts))
160
- files.push(FACET_MANIFEST_FILE)
161
-
162
- // Write skill files (Agent Skills directory convention: skills/<name>/SKILL.md)
163
- for (const skill of opts.skills) {
164
- await mkdir(join(targetDir, 'skills', skill), { recursive: true })
165
- await Bun.write(join(targetDir, `skills/${skill}/SKILL.md`), skillTemplate(skill))
166
- files.push(`skills/${skill}/SKILL.md`)
167
- }
168
-
169
- // Write agent files
170
- for (const agent of opts.agents) {
171
- await mkdir(join(targetDir, 'agents'), { recursive: true })
172
- await Bun.write(join(targetDir, `agents/${agent}.md`), agentTemplate(agent))
173
- files.push(`agents/${agent}.md`)
174
- }
175
-
176
- // Write command files
177
- for (const command of opts.commands) {
178
- await mkdir(join(targetDir, 'commands'), { recursive: true })
179
- await Bun.write(join(targetDir, `commands/${command}.md`), commandTemplate(command))
180
- files.push(`commands/${command}.md`)
181
- }
182
-
183
- return files
184
- }
@@ -1,144 +0,0 @@
1
- import { mkdir } from 'node:fs/promises'
2
- import { join } from 'node:path'
3
- import {
4
- type FacetManifest,
5
- hasFrontMatter,
6
- loadManifest,
7
- reconcile,
8
- scanAssets,
9
- writeManifest,
10
- } from '@agent-facets/core'
11
- import type { Command } from '../../commands.ts'
12
- import type { EditContext, EditOperation, EditResult, ReconciliationItem } from '../../tui/views/edit/edit-types.ts'
13
- import { agentTemplate, commandTemplate, skillTemplate } from '../create-scaffold.ts'
14
- import { resolveTargetDir } from '../resolve-dir.ts'
15
- import { runEditWizardInk } from './wizard.tsx'
16
-
17
- export async function buildEditContext(
18
- rootDir: string,
19
- ): Promise<{ ok: true; context: EditContext } | { ok: false; exitCode: number }> {
20
- // Load manifest
21
- const loadResult = await loadManifest(rootDir)
22
- if (!loadResult.ok) {
23
- // Hard error — show errors and exit
24
- console.error('Manifest is invalid:')
25
- for (const err of loadResult.errors) {
26
- console.error(` ${err.message}`)
27
- }
28
- console.error('\nFix facet.json and try again.')
29
- return { ok: false, exitCode: 1 }
30
- }
31
-
32
- const manifest = loadResult.data
33
-
34
- // Scan disk for assets
35
- const discovered = await scanAssets(rootDir)
36
-
37
- // Run reconciliation
38
- const recon = reconcile(manifest, discovered)
39
-
40
- // Build reconciliation items
41
- const items: ReconciliationItem[] = []
42
-
43
- for (const addition of recon.additions) {
44
- items.push({ kind: 'addition', type: addition.type, name: addition.name, path: addition.path })
45
- }
46
-
47
- for (const missing of recon.missing) {
48
- items.push({ kind: 'missing', type: missing.type, name: missing.name, expectedPath: missing.expectedPath })
49
- }
50
-
51
- // Check matched assets for front matter
52
- for (const matched of recon.matched) {
53
- const filePath = join(rootDir, matched.path)
54
- const content = await Bun.file(filePath).text()
55
- if (hasFrontMatter(content)) {
56
- items.push({ kind: 'front-matter', type: matched.type, name: matched.name, path: matched.path })
57
- }
58
- }
59
-
60
- return { ok: true, context: { rootDir, manifest, reconciliationItems: items } }
61
- }
62
-
63
- export async function applyOperations(
64
- manifest: FacetManifest,
65
- operations: EditOperation[],
66
- rootDir: string,
67
- ): Promise<void> {
68
- for (const op of operations) {
69
- switch (op.op) {
70
- case 'write-manifest':
71
- await writeManifest(manifest, rootDir)
72
- break
73
-
74
- case 'scaffold': {
75
- if (op.type === 'skills') {
76
- const dir = join(rootDir, 'skills', op.name)
77
- await mkdir(dir, { recursive: true })
78
- await Bun.write(join(dir, 'SKILL.md'), skillTemplate(op.name))
79
- } else if (op.type === 'agents') {
80
- await mkdir(join(rootDir, 'agents'), { recursive: true })
81
- await Bun.write(join(rootDir, `agents/${op.name}.md`), agentTemplate(op.name))
82
- } else if (op.type === 'commands') {
83
- await mkdir(join(rootDir, 'commands'), { recursive: true })
84
- await Bun.write(join(rootDir, `commands/${op.name}.md`), commandTemplate(op.name))
85
- }
86
- break
87
- }
88
-
89
- case 'delete-file': {
90
- const path =
91
- op.type === 'skills' ? join(rootDir, 'skills', op.name, 'SKILL.md') : join(rootDir, op.type, `${op.name}.md`)
92
- try {
93
- const { unlink } = await import('node:fs/promises')
94
- await unlink(path)
95
- } catch {
96
- // File already gone — that's fine
97
- }
98
- break
99
- }
100
-
101
- case 'strip-front-matter': {
102
- const { extractFrontMatter } = await import('@agent-facets/core')
103
- const filePath = join(rootDir, op.path)
104
- const raw = await Bun.file(filePath).text()
105
- const { content } = extractFrontMatter(raw)
106
- await Bun.write(filePath, content)
107
- break
108
- }
109
- }
110
- }
111
- }
112
-
113
- export const editCommand: Command = {
114
- name: 'edit',
115
- description: 'Edit a facet project interactively',
116
- usage: '[directory]',
117
- run: async (args: string[], _flags: Record<string, unknown>): Promise<number> => {
118
- const resolved = await resolveTargetDir(args[0], { mustExist: true, facetMustExist: true })
119
- if (!resolved.ok) {
120
- console.error(resolved.message)
121
- return 1
122
- }
123
-
124
- const rootDir = resolved.dir
125
- const displayDir = resolved.display
126
-
127
- const loaded = await buildEditContext(rootDir)
128
- if (!loaded.ok) return loaded.exitCode
129
-
130
- const result: EditResult = await runEditWizardInk(loaded.context)
131
-
132
- if (result.outcome === 'cancelled') {
133
- console.log('\nCancelled — no changes applied.')
134
- return 1
135
- }
136
-
137
- await applyOperations(result.manifest, result.operations, rootDir)
138
-
139
- console.log(`\nChanges applied to ${displayDir}`)
140
- console.log(`Run "facet build${args[0] ? ` ${displayDir}` : ''}" to validate your facet.`)
141
-
142
- return 0
143
- },
144
- }
@@ -1,74 +0,0 @@
1
- import { render } from 'ink'
2
- import type { AssetSectionKey } from '../../tui/context/form-state-context.ts'
3
- import { openInEditorSync } from '../../tui/editor.ts'
4
- import type { EditContext, EditResult } from '../../tui/views/edit/edit-types.ts'
5
- import type { EditWizardSnapshot } from '../../tui/views/edit/wizard.tsx'
6
- import { EditWizard } from '../../tui/views/edit/wizard.tsx'
7
-
8
- interface EditorRequest {
9
- section: AssetSectionKey
10
- name: string
11
- description: string
12
- }
13
-
14
- export async function runEditWizardInk(context: EditContext): Promise<EditResult> {
15
- let result: EditResult = { outcome: 'cancelled' }
16
- let snapshot: EditWizardSnapshot | undefined
17
- let pendingEditor: EditorRequest | null = null
18
- let done = false
19
-
20
- while (!done) {
21
- pendingEditor = null
22
-
23
- await new Promise<void>((resolve) => {
24
- const instance = render(
25
- <EditWizard
26
- context={context}
27
- snapshot={snapshot}
28
- onComplete={(r) => {
29
- result = r
30
- }}
31
- onSnapshot={(s) => {
32
- snapshot = s
33
- }}
34
- onRequestEditor={(section, name, description) => {
35
- pendingEditor = { section, name, description }
36
- instance.unmount()
37
- }}
38
- />,
39
- )
40
-
41
- instance.waitUntilExit().then(() => resolve())
42
- })
43
-
44
- if (pendingEditor) {
45
- const req = pendingEditor as EditorRequest
46
- const edited = openInEditorSync(req.description, `${req.name}.md`)
47
- if (snapshot) {
48
- snapshot = {
49
- ...snapshot,
50
- selectedItem: undefined,
51
- formState: snapshot.formState
52
- ? {
53
- ...snapshot.formState,
54
- assets: {
55
- ...snapshot.formState.assets,
56
- [req.section]: {
57
- ...snapshot.formState.assets[req.section],
58
- descriptions: {
59
- ...snapshot.formState.assets[req.section].descriptions,
60
- ...(edited !== null ? { [req.name]: edited.trim() } : {}),
61
- },
62
- },
63
- },
64
- }
65
- : undefined,
66
- }
67
- }
68
- } else {
69
- done = true
70
- }
71
- }
72
-
73
- return result
74
- }