@cat-factory/app 0.185.0 → 0.187.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/README.md +4 -1
- package/app/assets/css/main.css +2 -0
- package/app/assets/css/prose.css +135 -0
- package/app/components/board/AddTaskModal.vue +31 -1
- package/app/components/brainstorm/BrainstormWindow.vue +4 -37
- package/app/components/clarity/ClarityReviewWindow.vue +4 -37
- package/app/components/fragments/FragmentLibraryManager.vue +39 -2
- package/app/components/initiative/InitiativePlanReview.vue +351 -0
- package/app/components/initiative/InitiativeTrackerWindow.vue +12 -126
- package/app/components/panels/AgentStepDetail.vue +19 -122
- package/app/components/panels/inspector/TaskReviewTarget.vue +69 -0
- package/app/components/requirements/RequirementsReviewWindow.vue +4 -37
- package/app/composables/useProseComments.ts +126 -0
- package/app/composables/useStepApproval.ts +29 -85
- package/app/composables/useStepProse.spec.ts +67 -0
- package/app/composables/useStepProse.ts +22 -10
- package/app/modular/nav-contributions.spec.ts +3 -13
- package/app/modular/panels/inspector.logic.spec.ts +7 -0
- package/app/modular/panels/inspector.logic.ts +5 -0
- package/app/modular/panels/inspector.ts +2 -0
- package/app/stores/execution.ts +9 -0
- package/app/utils/initiative.spec.ts +24 -0
- package/i18n/locales/de.json +16 -3
- package/i18n/locales/en.json +17 -3
- package/i18n/locales/es.json +16 -3
- package/i18n/locales/fr.json +16 -3
- package/i18n/locales/he.json +16 -3
- package/i18n/locales/it.json +16 -3
- package/i18n/locales/ja.json +16 -3
- package/i18n/locales/pl.json +16 -3
- package/i18n/locales/tr.json +16 -3
- package/i18n/locales/uk.json +16 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -142,7 +142,10 @@ example ships in [`deploy/frontend`](../../deploy/frontend) (the `acme:security`
|
|
|
142
142
|
the board.
|
|
143
143
|
- **Inspector** (`components/panels` + `panels/inspector`) — per-block tabs:
|
|
144
144
|
structure, dependencies, model + fragment picker, live execution, and linked
|
|
145
|
-
docs/issues/scenarios. Decisions resolve via `DecisionModal`.
|
|
145
|
+
docs/issues/scenarios. Decisions resolve via `DecisionModal`. A `review` task
|
|
146
|
+
additionally leads with `TaskReviewTarget`, linking the pull request it reviews —
|
|
147
|
+
distinct from the execution panel's link to the PR a run PRODUCED, which a review
|
|
148
|
+
task never has.
|
|
146
149
|
- **Pipeline builder** (`components/pipeline`) — assemble/edit agent chains and
|
|
147
150
|
watch `PipelineProgress`. `PipelinePicker` (+ its `PipelinePreview` pane) is the
|
|
148
151
|
single way a pipeline is chosen anywhere — add-task, run settings, the recurring
|
package/app/assets/css/main.css
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
@import 'tailwindcss';
|
|
2
2
|
@import '@nuxt/ui';
|
|
3
|
+
/* The shared rendered-markdown reader presentation (see prose.css). */
|
|
4
|
+
@import './prose.css';
|
|
3
5
|
|
|
4
6
|
/* Toast text should be selectable. Reka UI sets `user-select: none` inline on
|
|
5
7
|
* the toast root (to support swipe-to-dismiss); since `user-select` is
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The rendered-markdown reader's presentation, shared by EVERY surface that reviews an agent's
|
|
3
|
+
* prose: the step-detail overlay (`AgentStepDetail`), the initiative tracker's plan-approval
|
|
4
|
+
* rail, and the clarity / requirements / brainstorm review windows. Global rather than scoped
|
|
5
|
+
* because the markup is injected with `v-html`, so a scoped sheet would need `:deep()` in each
|
|
6
|
+
* consumer — which is exactly what those windows each used to carry, five near-identical copies
|
|
7
|
+
* diverging a property at a time. "Consistent in look and feel across review surfaces" is the
|
|
8
|
+
* whole point, so the copy lives here once and a new reader adds `.reader-prose` and nothing else.
|
|
9
|
+
*
|
|
10
|
+
* `.reader-prose` styles the document; `.review-mode` turns each source-mapped block into a
|
|
11
|
+
* comment target (hover highlight + a "+" gutter affordance, GitHub-review style); `.cf-commented`
|
|
12
|
+
* / `.cf-selected` are the persistent markers `useProseComments` toggles.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/* Approval mode: each source-mapped block becomes a comment target — a hover
|
|
16
|
+
highlight + a "+" gutter affordance, GitHub-review style. */
|
|
17
|
+
.reader-prose.review-mode [data-src-start] {
|
|
18
|
+
position: relative;
|
|
19
|
+
cursor: pointer;
|
|
20
|
+
border-radius: 0.375rem;
|
|
21
|
+
transition: background 0.12s ease;
|
|
22
|
+
}
|
|
23
|
+
.reader-prose.review-mode [data-src-start]:hover {
|
|
24
|
+
background: rgb(99 102 241 / 0.08);
|
|
25
|
+
box-shadow: inset 2px 0 0 rgb(99 102 241 / 0.5);
|
|
26
|
+
}
|
|
27
|
+
.reader-prose.review-mode [data-src-start]::before {
|
|
28
|
+
content: '+';
|
|
29
|
+
position: absolute;
|
|
30
|
+
left: -1.4rem;
|
|
31
|
+
top: 0.1rem;
|
|
32
|
+
display: none;
|
|
33
|
+
height: 1.1rem;
|
|
34
|
+
width: 1.1rem;
|
|
35
|
+
align-items: center;
|
|
36
|
+
justify-content: center;
|
|
37
|
+
border-radius: 0.25rem;
|
|
38
|
+
background: rgb(99 102 241);
|
|
39
|
+
color: white;
|
|
40
|
+
font-size: 0.8rem;
|
|
41
|
+
line-height: 1;
|
|
42
|
+
}
|
|
43
|
+
.reader-prose.review-mode [data-src-start]:hover::before {
|
|
44
|
+
display: flex;
|
|
45
|
+
}
|
|
46
|
+
/* Persistent markers: amber for a block that already has a comment, indigo for
|
|
47
|
+
the block whose composer is currently open. */
|
|
48
|
+
.reader-prose .cf-commented {
|
|
49
|
+
background: rgb(234 179 8 / 0.1);
|
|
50
|
+
box-shadow: inset 2px 0 0 rgb(234 179 8 / 0.6);
|
|
51
|
+
}
|
|
52
|
+
.reader-prose .cf-selected {
|
|
53
|
+
background: rgb(99 102 241 / 0.12);
|
|
54
|
+
box-shadow: inset 2px 0 0 rgb(99 102 241 / 0.8);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* Styling for the markdown HTML injected via v-html (out of scoped reach without
|
|
58
|
+
:deep), kept close to the inspector's existing prose styling. */
|
|
59
|
+
.reader-prose p {
|
|
60
|
+
margin: 0.5rem 0;
|
|
61
|
+
}
|
|
62
|
+
.reader-prose ul,
|
|
63
|
+
.reader-prose ol {
|
|
64
|
+
margin: 0.5rem 0;
|
|
65
|
+
padding-left: 1.25rem;
|
|
66
|
+
}
|
|
67
|
+
.reader-prose ul {
|
|
68
|
+
list-style: disc;
|
|
69
|
+
}
|
|
70
|
+
.reader-prose ol {
|
|
71
|
+
list-style: decimal;
|
|
72
|
+
}
|
|
73
|
+
.reader-prose li {
|
|
74
|
+
margin: 0.2rem 0;
|
|
75
|
+
}
|
|
76
|
+
.reader-prose strong {
|
|
77
|
+
font-weight: 600;
|
|
78
|
+
color: rgb(226 232 240);
|
|
79
|
+
}
|
|
80
|
+
.reader-prose em {
|
|
81
|
+
font-style: italic;
|
|
82
|
+
}
|
|
83
|
+
.reader-prose code {
|
|
84
|
+
border-radius: 0.25rem;
|
|
85
|
+
background: rgb(30 41 59 / 0.8);
|
|
86
|
+
padding: 0.1rem 0.3rem;
|
|
87
|
+
font-family: ui-monospace, monospace;
|
|
88
|
+
font-size: 0.85em;
|
|
89
|
+
color: rgb(199 210 254);
|
|
90
|
+
}
|
|
91
|
+
.reader-prose pre {
|
|
92
|
+
margin: 0.6rem 0;
|
|
93
|
+
overflow: auto;
|
|
94
|
+
border-radius: 0.5rem;
|
|
95
|
+
background: rgb(2 6 23 / 0.6);
|
|
96
|
+
padding: 0.75rem 0.9rem;
|
|
97
|
+
}
|
|
98
|
+
.reader-prose pre code {
|
|
99
|
+
background: transparent;
|
|
100
|
+
padding: 0;
|
|
101
|
+
color: rgb(203 213 225);
|
|
102
|
+
}
|
|
103
|
+
.reader-prose blockquote {
|
|
104
|
+
margin: 0.6rem 0;
|
|
105
|
+
border-left: 3px solid rgb(99 102 241 / 0.5);
|
|
106
|
+
padding-left: 0.75rem;
|
|
107
|
+
color: rgb(148 163 184);
|
|
108
|
+
}
|
|
109
|
+
.reader-prose table {
|
|
110
|
+
margin: 0.6rem 0;
|
|
111
|
+
border-collapse: collapse;
|
|
112
|
+
font-size: 0.95em;
|
|
113
|
+
}
|
|
114
|
+
.reader-prose th,
|
|
115
|
+
.reader-prose td {
|
|
116
|
+
border: 1px solid rgb(51 65 85);
|
|
117
|
+
padding: 0.3rem 0.6rem;
|
|
118
|
+
}
|
|
119
|
+
.reader-prose th {
|
|
120
|
+
background: rgb(30 41 59 / 0.6);
|
|
121
|
+
font-weight: 600;
|
|
122
|
+
}
|
|
123
|
+
.reader-prose hr {
|
|
124
|
+
margin: 1rem 0;
|
|
125
|
+
border: none;
|
|
126
|
+
border-top: 1px solid rgb(51 65 85);
|
|
127
|
+
}
|
|
128
|
+
.reader-prose h1,
|
|
129
|
+
.reader-prose h2,
|
|
130
|
+
.reader-prose h3,
|
|
131
|
+
.reader-prose h4 {
|
|
132
|
+
margin: 0.6rem 0 0.3rem;
|
|
133
|
+
font-weight: 600;
|
|
134
|
+
color: rgb(226 232 240);
|
|
135
|
+
}
|
|
@@ -28,6 +28,8 @@ import ContextAttachmentFields from '~/components/context/ContextAttachmentField
|
|
|
28
28
|
import FragmentSelector from '~/components/fragments/FragmentSelector.vue'
|
|
29
29
|
import RiskPolicyPicker from '~/components/riskPolicy/RiskPolicyPicker.vue'
|
|
30
30
|
import { parseConflict } from '~/composables/usePipelineErrorToast'
|
|
31
|
+
import { apiErrorEnvelope } from '~/composables/api/errors'
|
|
32
|
+
import type { ReviewTargetReason } from '@cat-factory/contracts'
|
|
31
33
|
import { pipelineAllowedForManualStart } from '~/utils/pipeline'
|
|
32
34
|
|
|
33
35
|
const ui = useUiStore()
|
|
@@ -680,7 +682,7 @@ async function submitCreate(acknowledgeReviewDebt: boolean) {
|
|
|
680
682
|
}
|
|
681
683
|
toast.add({
|
|
682
684
|
title: t('board.addTask.addFailedTitle'),
|
|
683
|
-
description: e instanceof Error ? e.message : String(e),
|
|
685
|
+
description: reviewTargetMessage(e) ?? (e instanceof Error ? e.message : String(e)),
|
|
684
686
|
icon: 'i-lucide-triangle-alert',
|
|
685
687
|
color: 'error',
|
|
686
688
|
})
|
|
@@ -689,6 +691,34 @@ async function submitCreate(acknowledgeReviewDebt: boolean) {
|
|
|
689
691
|
}
|
|
690
692
|
}
|
|
691
693
|
|
|
694
|
+
// The backend validates a review task's target PR against the service's repo before creating
|
|
695
|
+
// anything, and refuses with a machine-readable reason. Map it to translated copy here (the
|
|
696
|
+
// backend does not localize prose); an unrecognised failure keeps the raw message.
|
|
697
|
+
// Exhaustive over the closed union, so a new reason fails the typecheck rather than silently
|
|
698
|
+
// falling through to an English sentence from the server.
|
|
699
|
+
// Each message names what the user got wrong, so a detail the backend didn't send would leave a
|
|
700
|
+
// hole in the sentence: those cases return null and the server's own prose is shown instead.
|
|
701
|
+
const REVIEW_TARGET_MESSAGES: Record<
|
|
702
|
+
ReviewTargetReason,
|
|
703
|
+
(details: Record<string, unknown>) => string | null
|
|
704
|
+
> = {
|
|
705
|
+
review_pr_not_found: (d) =>
|
|
706
|
+
typeof d.prNumber === 'number'
|
|
707
|
+
? t('board.addTask.review.prNotFound', { number: d.prNumber })
|
|
708
|
+
: null,
|
|
709
|
+
review_pr_repo_mismatch: (d) =>
|
|
710
|
+
typeof d.expected === 'string'
|
|
711
|
+
? t('board.addTask.review.prRepoMismatch', { repo: d.expected })
|
|
712
|
+
: null,
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
function reviewTargetMessage(error: unknown): string | null {
|
|
716
|
+
const details = (apiErrorEnvelope(error)?.details ?? {}) as Record<string, unknown>
|
|
717
|
+
const reason = details.reason
|
|
718
|
+
if (typeof reason !== 'string') return null
|
|
719
|
+
return REVIEW_TARGET_MESSAGES[reason as ReviewTargetReason]?.(details) ?? null
|
|
720
|
+
}
|
|
721
|
+
|
|
692
722
|
/** Turn a parsed review-debt friction 409 into the dialog context (see ReviewFrictionDialog.vue). */
|
|
693
723
|
function openReviewFrictionDialog(conflict: NonNullable<ReturnType<typeof parseConflict>>) {
|
|
694
724
|
const details = conflict.details
|
|
@@ -610,41 +610,8 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
610
610
|
.pl-5\.5 {
|
|
611
611
|
padding-left: 1.375rem;
|
|
612
612
|
}
|
|
613
|
-
/*
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
}
|
|
618
|
-
.reader-prose :deep(ul),
|
|
619
|
-
.reader-prose :deep(ol) {
|
|
620
|
-
margin: 0.4rem 0;
|
|
621
|
-
padding-left: 1.25rem;
|
|
622
|
-
list-style: revert;
|
|
623
|
-
}
|
|
624
|
-
.reader-prose :deep(li) {
|
|
625
|
-
margin: 0.2rem 0;
|
|
626
|
-
}
|
|
627
|
-
.reader-prose :deep(strong) {
|
|
628
|
-
color: rgb(226 232 240);
|
|
629
|
-
font-weight: 600;
|
|
630
|
-
}
|
|
631
|
-
.reader-prose :deep(code) {
|
|
632
|
-
border-radius: 0.25rem;
|
|
633
|
-
background: rgb(2 6 23 / 0.6);
|
|
634
|
-
padding: 0.05rem 0.3rem;
|
|
635
|
-
font-size: 0.85em;
|
|
636
|
-
}
|
|
637
|
-
.reader-prose :deep(pre) {
|
|
638
|
-
margin: 0.5rem 0;
|
|
639
|
-
overflow-x: auto;
|
|
640
|
-
border-radius: 0.5rem;
|
|
641
|
-
background: rgb(2 6 23 / 0.6);
|
|
642
|
-
padding: 0.75rem;
|
|
643
|
-
}
|
|
644
|
-
.reader-prose :deep(blockquote) {
|
|
645
|
-
margin: 0.5rem 0;
|
|
646
|
-
border-left: 2px solid rgb(51 65 85);
|
|
647
|
-
padding-left: 0.75rem;
|
|
648
|
-
color: rgb(148 163 184);
|
|
649
|
-
}
|
|
613
|
+
/* The rendered-markdown presentation is the SHARED global `.reader-prose` sheet
|
|
614
|
+
(`assets/css/prose.css`), not a local copy: this reader shows the same agent-authored
|
|
615
|
+
markdown the step reader does, and a per-window duplicate is how the review surfaces
|
|
616
|
+
drift apart one property at a time. */
|
|
650
617
|
</style>
|
|
@@ -658,41 +658,8 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
658
658
|
.pl-5\.5 {
|
|
659
659
|
padding-left: 1.375rem;
|
|
660
660
|
}
|
|
661
|
-
/*
|
|
662
|
-
prose
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
}
|
|
666
|
-
.reader-prose :deep(ul),
|
|
667
|
-
.reader-prose :deep(ol) {
|
|
668
|
-
margin: 0.4rem 0;
|
|
669
|
-
padding-left: 1.25rem;
|
|
670
|
-
list-style: revert;
|
|
671
|
-
}
|
|
672
|
-
.reader-prose :deep(li) {
|
|
673
|
-
margin: 0.2rem 0;
|
|
674
|
-
}
|
|
675
|
-
.reader-prose :deep(strong) {
|
|
676
|
-
color: rgb(226 232 240);
|
|
677
|
-
font-weight: 600;
|
|
678
|
-
}
|
|
679
|
-
.reader-prose :deep(code) {
|
|
680
|
-
border-radius: 0.25rem;
|
|
681
|
-
background: rgb(2 6 23 / 0.6);
|
|
682
|
-
padding: 0.05rem 0.3rem;
|
|
683
|
-
font-size: 0.85em;
|
|
684
|
-
}
|
|
685
|
-
.reader-prose :deep(pre) {
|
|
686
|
-
margin: 0.5rem 0;
|
|
687
|
-
overflow-x: auto;
|
|
688
|
-
border-radius: 0.5rem;
|
|
689
|
-
background: rgb(2 6 23 / 0.6);
|
|
690
|
-
padding: 0.75rem;
|
|
691
|
-
}
|
|
692
|
-
.reader-prose :deep(blockquote) {
|
|
693
|
-
margin: 0.5rem 0;
|
|
694
|
-
border-left: 2px solid rgb(51 65 85);
|
|
695
|
-
padding-left: 0.75rem;
|
|
696
|
-
color: rgb(148 163 184);
|
|
697
|
-
}
|
|
661
|
+
/* The rendered-markdown presentation is the SHARED global `.reader-prose` sheet
|
|
662
|
+
(`assets/css/prose.css`), not a local copy: this reader shows the same agent-authored
|
|
663
|
+
markdown the step reader does, and a per-window duplicate is how the review surfaces
|
|
664
|
+
drift apart one property at a time. */
|
|
698
665
|
</style>
|
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
ResolvedFragment,
|
|
15
15
|
} from '~/types/domain'
|
|
16
16
|
import { useFragmentLibrary, useFragmentLibraryStore } from '~/stores/fragmentLibrary'
|
|
17
|
+
import { showOverrideField } from '~/utils/uiMode'
|
|
17
18
|
import GitHubRepoSearchSelect from '~/components/github/GitHubRepoSearchSelect.vue'
|
|
18
19
|
import RepoTreeBrowser from '~/components/github/RepoTreeBrowser.vue'
|
|
19
20
|
import GitHubDocUrlImport from '~/components/fragments/GitHubDocUrlImport.vue'
|
|
@@ -131,7 +132,7 @@ const linkingDoc = ref(false)
|
|
|
131
132
|
const linkingSource = ref(false)
|
|
132
133
|
|
|
133
134
|
// ---- create a hand-authored fragment --------------------------------------
|
|
134
|
-
const draft = ref({ title: '', summary: '', body: '', tags: '' })
|
|
135
|
+
const draft = ref({ title: '', summary: '', body: '', brief: '', tags: '' })
|
|
135
136
|
const draftValid = computed(
|
|
136
137
|
() => draft.value.title.trim() && draft.value.summary.trim() && draft.value.body.trim(),
|
|
137
138
|
)
|
|
@@ -167,6 +168,7 @@ const editDraft = ref<{
|
|
|
167
168
|
title: string
|
|
168
169
|
summary: string
|
|
169
170
|
body: string
|
|
171
|
+
brief: string
|
|
170
172
|
tags: string
|
|
171
173
|
} | null>(null)
|
|
172
174
|
function startEdit(f: (typeof library.fragments)[number]) {
|
|
@@ -175,8 +177,10 @@ function startEdit(f: (typeof library.fragments)[number]) {
|
|
|
175
177
|
title: f.title,
|
|
176
178
|
summary: f.summary,
|
|
177
179
|
body: f.body,
|
|
180
|
+
brief: f.brief ?? '',
|
|
178
181
|
tags: (f.tags ?? []).join(', '),
|
|
179
182
|
}
|
|
183
|
+
showEditBrief.value = showOverrideField(uiMode.isAdvanced, f.brief ?? null)
|
|
180
184
|
}
|
|
181
185
|
function cancelEdit() {
|
|
182
186
|
editDraft.value = null
|
|
@@ -188,6 +192,19 @@ const editValid = computed(
|
|
|
188
192
|
!!editDraft.value.summary.trim() &&
|
|
189
193
|
!!editDraft.value.body.trim(),
|
|
190
194
|
)
|
|
195
|
+
// The linked SHORT VERSION is an OVERRIDE of what the platform does by default (condense a
|
|
196
|
+
// long standard automatically, fold a short one in full), so it follows the override rule:
|
|
197
|
+
// hidden in basic mode while unset, revealed as soon as the fragment carries one — a
|
|
198
|
+
// basic-mode curator is never left unable to see or clear a brief a teammate linked.
|
|
199
|
+
//
|
|
200
|
+
// Both flags are LATCHED at the moment the form opens rather than tracking the live draft.
|
|
201
|
+
// Recomputing per keystroke makes the control delete itself the instant a basic-mode curator
|
|
202
|
+
// empties it — mid-edit, under the cursor, on the one interaction (clearing, to hand the
|
|
203
|
+
// standard back to auto-generation) the rule exists to keep reachable.
|
|
204
|
+
const uiMode = useUiModeStore()
|
|
205
|
+
const showEditBrief = ref(false)
|
|
206
|
+
const showDraftBrief = computed(() => showOverrideField(uiMode.isAdvanced, null))
|
|
207
|
+
|
|
191
208
|
async function saveEdit() {
|
|
192
209
|
const d = editDraft.value
|
|
193
210
|
if (!d || !editValid.value) return
|
|
@@ -197,6 +214,9 @@ async function saveEdit() {
|
|
|
197
214
|
title: d.title.trim(),
|
|
198
215
|
summary: d.summary.trim(),
|
|
199
216
|
body: d.body.trim(),
|
|
217
|
+
// Always sent, so clearing the box UNLINKS the short version and hands the
|
|
218
|
+
// standard back to auto-generation (an omitted key would leave the old one).
|
|
219
|
+
brief: d.brief.trim(),
|
|
200
220
|
tags: d.tags
|
|
201
221
|
.split(',')
|
|
202
222
|
.map((t) => t.trim())
|
|
@@ -218,12 +238,13 @@ async function createFragment() {
|
|
|
218
238
|
title: draft.value.title.trim(),
|
|
219
239
|
summary: draft.value.summary.trim(),
|
|
220
240
|
body: draft.value.body.trim(),
|
|
241
|
+
brief: draft.value.brief.trim() || undefined,
|
|
221
242
|
tags: draft.value.tags
|
|
222
243
|
.split(',')
|
|
223
244
|
.map((t) => t.trim())
|
|
224
245
|
.filter(Boolean),
|
|
225
246
|
})
|
|
226
|
-
draft.value = { title: '', summary: '', body: '', tags: '' }
|
|
247
|
+
draft.value = { title: '', summary: '', body: '', brief: '', tags: '' }
|
|
227
248
|
toast.add({ title: t('fragments.toast.added'), icon: 'i-lucide-check' })
|
|
228
249
|
} catch (e) {
|
|
229
250
|
notifyError(t('fragments.toast.addFailed'), e)
|
|
@@ -701,6 +722,14 @@ async function unlinkSource(id: string) {
|
|
|
701
722
|
:placeholder="t('fragments.authored.bodyPlaceholder')"
|
|
702
723
|
:rows="4"
|
|
703
724
|
/>
|
|
725
|
+
<div v-if="showEditBrief" class="flex flex-col gap-1">
|
|
726
|
+
<UTextarea
|
|
727
|
+
v-model="editDraft.brief"
|
|
728
|
+
:placeholder="t('fragments.authored.briefPlaceholder')"
|
|
729
|
+
:rows="2"
|
|
730
|
+
/>
|
|
731
|
+
<p class="text-xs text-slate-500">{{ t('fragments.authored.briefHint') }}</p>
|
|
732
|
+
</div>
|
|
704
733
|
<UInput
|
|
705
734
|
v-model="editDraft.tags"
|
|
706
735
|
:placeholder="t('fragments.authored.tagsPlaceholder')"
|
|
@@ -799,6 +828,14 @@ async function unlinkSource(id: string) {
|
|
|
799
828
|
:placeholder="t('fragments.authored.bodyPlaceholder')"
|
|
800
829
|
:rows="4"
|
|
801
830
|
/>
|
|
831
|
+
<div v-if="showDraftBrief" class="flex flex-col gap-1">
|
|
832
|
+
<UTextarea
|
|
833
|
+
v-model="draft.brief"
|
|
834
|
+
:placeholder="t('fragments.authored.briefPlaceholder')"
|
|
835
|
+
:rows="2"
|
|
836
|
+
/>
|
|
837
|
+
<p class="text-xs text-slate-500">{{ t('fragments.authored.briefHint') }}</p>
|
|
838
|
+
</div>
|
|
802
839
|
<UInput v-model="draft.tags" :placeholder="t('fragments.authored.tagsPlaceholder')" />
|
|
803
840
|
<UButton
|
|
804
841
|
icon="i-lucide-plus"
|