@happyvertical/smrt-projects 0.60.8 → 0.60.10
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 +20 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +297 -75
- package/dist/smrt-knowledge.json +3 -3
- package/dist/smrt-knowledge.okf.md +131 -0
- package/dist/svelte/ProjectInterview.svelte +387 -0
- package/dist/svelte/ProjectInterview.svelte.d.ts +26 -0
- package/dist/svelte/ProjectInterview.svelte.d.ts.map +1 -0
- package/dist/svelte/i18n.d.ts +21 -0
- package/dist/svelte/i18n.d.ts.map +1 -1
- package/dist/svelte/i18n.js +21 -0
- package/dist/svelte/index.d.ts +4 -2
- package/dist/svelte/index.d.ts.map +1 -1
- package/dist/svelte/index.js +3 -1
- package/dist/svelte/project-governance-types.d.ts +25 -0
- package/dist/svelte/project-governance-types.d.ts.map +1 -1
- package/package.json +18 -18
package/dist/smrt-knowledge.json
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
"sensitiveFieldsExcluded": true,
|
|
4
4
|
"generatedAt": "1970-01-01T00:00:00.000Z",
|
|
5
5
|
"packageName": "@happyvertical/smrt-projects",
|
|
6
|
-
"packageVersion": "0.60.
|
|
6
|
+
"packageVersion": "0.60.10",
|
|
7
7
|
"sourceManifestPath": "dist/manifest.json",
|
|
8
8
|
"agentDocPath": "AGENTS.md",
|
|
9
9
|
"sourceHashes": {
|
|
10
|
-
"manifest": "
|
|
11
|
-
"packageJson": "
|
|
10
|
+
"manifest": "041080f88a30cc8780dd0337e318cfa66f091e1390b7810d7bbbcf4011f12b8a",
|
|
11
|
+
"packageJson": "2f4bcb63845c682e3c304875322e928e745147238d13f06b9d638ba99ab8a271",
|
|
12
12
|
"agents": "2879830c12902195bd4f887cf00bdabbe8e7216ebafe2a9b2d007a00d137435c"
|
|
13
13
|
},
|
|
14
14
|
"exports": [
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: "Component"
|
|
3
|
+
title: "@happyvertical/smrt-projects"
|
|
4
|
+
description: "Provider-agnostic project management models (Issues, PRs, Repositories, Projects) for SMRT framework"
|
|
5
|
+
status: "stable"
|
|
6
|
+
generated:
|
|
7
|
+
by: "smrt/scripts/generate-knowledge-okf.ts"
|
|
8
|
+
at: "2026-11-05T15:48:19Z"
|
|
9
|
+
audience: "internal"
|
|
10
|
+
evidence_level: "source-inspected"
|
|
11
|
+
source_revision: "sha256:800a0e90c386ac2e2fe701004092478c3c53c1acfb8c257f86d675003e9ccd03"
|
|
12
|
+
sources:
|
|
13
|
+
- id: "manifest"
|
|
14
|
+
resource: "https://github.com/happyvertical/smrt/blob/main/packages/projects/package.json"
|
|
15
|
+
title: "@happyvertical/smrt-projects package manifest"
|
|
16
|
+
- id: "agents"
|
|
17
|
+
resource: "https://github.com/happyvertical/smrt/blob/main/packages/projects/AGENTS.md"
|
|
18
|
+
title: "@happyvertical/smrt-projects packages/projects/AGENTS.md"
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
# @happyvertical/smrt-projects
|
|
22
|
+
|
|
23
|
+
## Package
|
|
24
|
+
|
|
25
|
+
- Directory: `packages/projects`
|
|
26
|
+
- Private: no
|
|
27
|
+
- Object source: `domain-artifact`
|
|
28
|
+
- Evidence level: `source-inspected`
|
|
29
|
+
|
|
30
|
+
## Documentation
|
|
31
|
+
|
|
32
|
+
- `packages/projects/AGENTS.md`
|
|
33
|
+
|
|
34
|
+
## Framework dependencies
|
|
35
|
+
|
|
36
|
+
- [smrt-cli](/components/smrt-cli.md)
|
|
37
|
+
- [smrt-config](/components/smrt-config.md)
|
|
38
|
+
- [smrt-core](/components/smrt-core.md)
|
|
39
|
+
- [smrt-prompts](/components/smrt-prompts.md)
|
|
40
|
+
- [smrt-subscriptions](/components/smrt-subscriptions.md)
|
|
41
|
+
- [smrt-svelte](/components/smrt-svelte.md)
|
|
42
|
+
- [smrt-tenancy](/components/smrt-tenancy.md)
|
|
43
|
+
- [smrt-timesheets](/components/smrt-timesheets.md)
|
|
44
|
+
- [smrt-types](/components/smrt-types.md)
|
|
45
|
+
- [smrt-ui](/components/smrt-ui.md)
|
|
46
|
+
- [smrt-vitest](/components/smrt-vitest.md)
|
|
47
|
+
|
|
48
|
+
## SDK dependencies
|
|
49
|
+
|
|
50
|
+
- `@happyvertical/ai`
|
|
51
|
+
- `@happyvertical/logger`
|
|
52
|
+
- `@happyvertical/projects`
|
|
53
|
+
- `@happyvertical/repos`
|
|
54
|
+
- `@happyvertical/sql`
|
|
55
|
+
- `@happyvertical/utils`
|
|
56
|
+
|
|
57
|
+
## Exports
|
|
58
|
+
|
|
59
|
+
- `.`
|
|
60
|
+
- `./manifest`
|
|
61
|
+
- `./manifest.json`
|
|
62
|
+
- `./playground`
|
|
63
|
+
- `./svelte`
|
|
64
|
+
- `./ui`
|
|
65
|
+
|
|
66
|
+
## Models
|
|
67
|
+
|
|
68
|
+
- `AssistanceRequest` (collection `assistancerequests`, extends `SmrtObject`)
|
|
69
|
+
- `AssistanceRequestCollection` (collection `assistancerequests`, extends `SmrtCollection`)
|
|
70
|
+
- `AssistanceRequestEvent` (collection `assistancerequestevents`, extends `SmrtObject`)
|
|
71
|
+
- `AssistanceRequestEventCollection` (collection `assistancerequestevents`, extends `SmrtCollection`)
|
|
72
|
+
- `Comment` (collection `comments`, extends `SmrtObject`)
|
|
73
|
+
- `CommentCollection` (collection `comments`, extends `SmrtCollection`)
|
|
74
|
+
- `DevelopmentRequest` (collection `developmentrequests`, extends `SmrtObject`)
|
|
75
|
+
- `DevelopmentRequestCollection` (collection `developmentrequests`, extends `SmrtCollection`)
|
|
76
|
+
- `DevelopmentRequestHistory` (collection `developmentrequesthistories`, extends `SmrtObject`)
|
|
77
|
+
- `DevelopmentRequestHistoryCollection` (collection `developmentrequesthistories`, extends `SmrtCollection`)
|
|
78
|
+
- `DevelopmentRequestWorkLink` (collection `developmentrequestworklinks`, extends `SmrtObject`)
|
|
79
|
+
- `DevelopmentRequestWorkLinkCollection` (collection `developmentrequestworklinks`, extends `SmrtCollection`)
|
|
80
|
+
- `Issue` (collection `issues`, extends `SmrtObject`, STI table)
|
|
81
|
+
- `IssueCollection` (collection `issues`, extends `SmrtCollection`)
|
|
82
|
+
- `Label` (collection `labels`, extends `SmrtObject`)
|
|
83
|
+
- `PreviewApproval` (collection `previewapprovals`, extends `SmrtObject`)
|
|
84
|
+
- `PreviewApprovalCollection` (collection `previewapprovals`, extends `SmrtCollection`)
|
|
85
|
+
- `Project` (collection `projects`, extends `SmrtObject`)
|
|
86
|
+
- `ProjectCollection` (collection `projects`, extends `SmrtCollection`)
|
|
87
|
+
- `ProjectDecision` (collection `projectdecisions`, extends `SmrtPolymorphicAssociation`)
|
|
88
|
+
- `ProjectDecisionCollection` (collection `projectdecisions`, extends `SmrtCollection`)
|
|
89
|
+
- `ProjectDeliveryEvent` (collection `projectdeliveryevents`, extends `SmrtObject`)
|
|
90
|
+
- `ProjectDeliveryEventCollection` (collection `projectdeliveryevents`, extends `SmrtCollection`)
|
|
91
|
+
- `ProjectGovernanceEvent` (collection `projectgovernanceevents`, extends `SmrtPolymorphicAssociation`)
|
|
92
|
+
- `ProjectGovernanceEventCollection` (collection `projectgovernanceevents`, extends `SmrtCollection`)
|
|
93
|
+
- `ProjectIntegration` (collection `projectintegrations`, extends `SmrtObject`)
|
|
94
|
+
- `ProjectIntegrationAudit` (collection `projectintegrationaudits`, extends `SmrtObject`)
|
|
95
|
+
- `ProjectIntegrationAuditCollection` (collection `projectintegrationaudits`, extends `SmrtCollection`)
|
|
96
|
+
- `ProjectIntegrationCollection` (collection `projectintegrations`, extends `SmrtCollection`)
|
|
97
|
+
- `ProjectQuestion` (collection `projectquestions`, extends `SmrtPolymorphicAssociation`)
|
|
98
|
+
- `ProjectQuestionCollection` (collection `projectquestions`, extends `SmrtCollection`)
|
|
99
|
+
- `ProjectSpecification` (collection `projectspecifications`, extends `SmrtPolymorphicAssociation`)
|
|
100
|
+
- `ProjectSpecificationCollection` (collection `projectspecifications`, extends `SmrtCollection`)
|
|
101
|
+
- `PullRequest` (collection `issues`, extends `Issue`, STI table)
|
|
102
|
+
- `PullRequestCollection` (collection `issues`, extends `SmrtCollection`)
|
|
103
|
+
- `Repository` (collection `repositories`, extends `SmrtObject`)
|
|
104
|
+
- `RepositoryCollection` (collection `repositories`, extends `SmrtCollection`)
|
|
105
|
+
|
|
106
|
+
## MCP tools
|
|
107
|
+
|
|
108
|
+
- `comment_get`: get on `@happyvertical/smrt-projects:Comment`
|
|
109
|
+
- `comment_list`: list on `@happyvertical/smrt-projects:Comment`
|
|
110
|
+
- `issue_get`: get on `@happyvertical/smrt-projects:Issue`
|
|
111
|
+
- `issue_incorporatefeedback`: incorporateFeedback on `@happyvertical/smrt-projects:Issue`
|
|
112
|
+
- `issue_list`: list on `@happyvertical/smrt-projects:Issue`
|
|
113
|
+
- `issue_sync`: sync on `@happyvertical/smrt-projects:Issue`
|
|
114
|
+
- `label_create`: create on `@happyvertical/smrt-projects:Label`
|
|
115
|
+
- `label_get`: get on `@happyvertical/smrt-projects:Label`
|
|
116
|
+
- `label_list`: list on `@happyvertical/smrt-projects:Label`
|
|
117
|
+
- `project_additem`: addItem on `@happyvertical/smrt-projects:Project`
|
|
118
|
+
- `project_get`: get on `@happyvertical/smrt-projects:Project`
|
|
119
|
+
- `project_list`: list on `@happyvertical/smrt-projects:Project`
|
|
120
|
+
- `project_sync`: sync on `@happyvertical/smrt-projects:Project`
|
|
121
|
+
- `project_updateitemstatus`: updateItemStatus on `@happyvertical/smrt-projects:Project`
|
|
122
|
+
- `projectintegration_get`: get on `@happyvertical/smrt-projects:ProjectIntegration`
|
|
123
|
+
- `projectintegration_list`: list on `@happyvertical/smrt-projects:ProjectIntegration`
|
|
124
|
+
- `pullrequest_get`: get on `@happyvertical/smrt-projects:PullRequest`
|
|
125
|
+
- `pullrequest_list`: list on `@happyvertical/smrt-projects:PullRequest`
|
|
126
|
+
- `pullrequest_merge`: merge on `@happyvertical/smrt-projects:PullRequest`
|
|
127
|
+
- `pullrequest_summarize`: summarize on `@happyvertical/smrt-projects:PullRequest`
|
|
128
|
+
- `pullrequest_sync`: sync on `@happyvertical/smrt-projects:PullRequest`
|
|
129
|
+
- `repository_get`: get on `@happyvertical/smrt-projects:Repository`
|
|
130
|
+
- `repository_list`: list on `@happyvertical/smrt-projects:Repository`
|
|
131
|
+
- `repository_sync`: sync on `@happyvertical/smrt-projects:Repository`
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Form, Input, Textarea } from '@happyvertical/smrt-ui/forms';
|
|
3
|
+
import { useI18n } from '@happyvertical/smrt-ui/i18n';
|
|
4
|
+
import { Button } from '@happyvertical/smrt-ui/ui';
|
|
5
|
+
import { tick } from 'svelte';
|
|
6
|
+
import { M } from './i18n.js';
|
|
7
|
+
import type {
|
|
8
|
+
ProjectDecisionResolveInput,
|
|
9
|
+
ProjectDecisionView,
|
|
10
|
+
ProjectInterviewAdvanceDetail,
|
|
11
|
+
ProjectInterviewFraming,
|
|
12
|
+
ProjectQuestionAnswerInput,
|
|
13
|
+
ProjectQuestionView,
|
|
14
|
+
} from './project-governance-types.js';
|
|
15
|
+
|
|
16
|
+
export interface Props {
|
|
17
|
+
/** Authorized questions, in the order the host wants them asked. */
|
|
18
|
+
questions?: ProjectQuestionView[];
|
|
19
|
+
/** Authorized decisions, asked after the questions in the host's order. */
|
|
20
|
+
decisions?: ProjectDecisionView[];
|
|
21
|
+
/** Disable submissions and navigation while the host saves a change. */
|
|
22
|
+
busy?: boolean;
|
|
23
|
+
/** Plain-language framing keyed by question or decision id. */
|
|
24
|
+
framing?: Record<string, ProjectInterviewFraming>;
|
|
25
|
+
/**
|
|
26
|
+
* Zero-based record to open first (e.g. a resumed position). Defaults to the
|
|
27
|
+
* first open record; read once on mount.
|
|
28
|
+
*/
|
|
29
|
+
startIndex?: number;
|
|
30
|
+
/** Save an explicit answer with the observed revision. */
|
|
31
|
+
onanswer?: (input: ProjectQuestionAnswerInput) => void | Promise<void>;
|
|
32
|
+
/** Save a selected outcome and optional rationale with the observed revision. */
|
|
33
|
+
ondecide?: (input: ProjectDecisionResolveInput) => void | Promise<void>;
|
|
34
|
+
/** Called whenever the interview moves to another record or completes. */
|
|
35
|
+
onadvance?: (detail: ProjectInterviewAdvanceDetail) => void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
type Step =
|
|
39
|
+
| { kind: 'question'; key: string; record: ProjectQuestionView }
|
|
40
|
+
| { kind: 'decision'; key: string; record: ProjectDecisionView };
|
|
41
|
+
|
|
42
|
+
let {
|
|
43
|
+
questions = [],
|
|
44
|
+
decisions = [],
|
|
45
|
+
busy = false,
|
|
46
|
+
framing = {},
|
|
47
|
+
startIndex,
|
|
48
|
+
onanswer,
|
|
49
|
+
ondecide,
|
|
50
|
+
onadvance,
|
|
51
|
+
}: Props = $props();
|
|
52
|
+
|
|
53
|
+
const { t } = useI18n();
|
|
54
|
+
|
|
55
|
+
const steps = $derived<Step[]>([
|
|
56
|
+
...questions.map((record) => ({
|
|
57
|
+
kind: 'question' as const,
|
|
58
|
+
key: `question:${record.id}`,
|
|
59
|
+
record,
|
|
60
|
+
})),
|
|
61
|
+
...decisions.map((record) => ({
|
|
62
|
+
kind: 'decision' as const,
|
|
63
|
+
key: `decision:${record.id}`,
|
|
64
|
+
record,
|
|
65
|
+
})),
|
|
66
|
+
]);
|
|
67
|
+
const total = $derived(steps.length);
|
|
68
|
+
const answeredCount = $derived(
|
|
69
|
+
steps.filter((step) => step.record.status !== 'open').length,
|
|
70
|
+
);
|
|
71
|
+
const openCount = $derived(total - answeredCount);
|
|
72
|
+
|
|
73
|
+
function firstOpenFrom(from: number): number {
|
|
74
|
+
for (let index = Math.max(0, from); index < steps.length; index++) {
|
|
75
|
+
if (steps[index].record.status === 'open') return index;
|
|
76
|
+
}
|
|
77
|
+
return steps.length;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function initialCursor(): { key: string | null; index: number } {
|
|
81
|
+
const index =
|
|
82
|
+
startIndex === undefined
|
|
83
|
+
? firstOpenFrom(0)
|
|
84
|
+
: Math.min(Math.max(0, Math.trunc(startIndex)), steps.length);
|
|
85
|
+
return { key: steps[index]?.key ?? null, index };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Track the current record by identity so host re-ordering or removal of an
|
|
89
|
+
// answered record never jumps the person to an unrelated question.
|
|
90
|
+
let cursor = $state(initialCursor());
|
|
91
|
+
const position = $derived.by(() => {
|
|
92
|
+
const found = cursor.key
|
|
93
|
+
? steps.findIndex((step) => step.key === cursor.key)
|
|
94
|
+
: -1;
|
|
95
|
+
return found >= 0 ? found : Math.min(cursor.index, total);
|
|
96
|
+
});
|
|
97
|
+
const current = $derived<Step | null>(steps[position] ?? null);
|
|
98
|
+
|
|
99
|
+
// Keep the fallback index aligned with the record's latest position so a later
|
|
100
|
+
// withdrawal resumes from where that record actually was.
|
|
101
|
+
$effect(() => {
|
|
102
|
+
if (
|
|
103
|
+
cursor.key &&
|
|
104
|
+
steps[position]?.key === cursor.key &&
|
|
105
|
+
cursor.index !== position
|
|
106
|
+
) {
|
|
107
|
+
cursor = { key: cursor.key, index: position };
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
let pendingKey = $state<string | null>(null);
|
|
112
|
+
let selections = $state<Record<string, string>>({});
|
|
113
|
+
let heading = $state<HTMLElement | null>(null);
|
|
114
|
+
let focusRequested = false;
|
|
115
|
+
|
|
116
|
+
// Motion is opt-in: assume reduced motion until the browser says otherwise.
|
|
117
|
+
let reducedMotion = $state(true);
|
|
118
|
+
$effect(() => {
|
|
119
|
+
if (typeof window === 'undefined' || !window.matchMedia) return;
|
|
120
|
+
const query = window.matchMedia('(prefers-reduced-motion: reduce)');
|
|
121
|
+
reducedMotion = query.matches;
|
|
122
|
+
const update = (event: MediaQueryListEvent) => {
|
|
123
|
+
reducedMotion = event.matches;
|
|
124
|
+
};
|
|
125
|
+
query.addEventListener?.('change', update);
|
|
126
|
+
return () => query.removeEventListener?.('change', update);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
function moveTo(
|
|
130
|
+
index: number,
|
|
131
|
+
reason: ProjectInterviewAdvanceDetail['reason'],
|
|
132
|
+
) {
|
|
133
|
+
const next = Math.min(Math.max(0, index), total);
|
|
134
|
+
const step = steps[next] ?? null;
|
|
135
|
+
cursor = { key: step?.key ?? null, index: next };
|
|
136
|
+
pendingKey = null;
|
|
137
|
+
focusRequested = true;
|
|
138
|
+
onadvance?.({
|
|
139
|
+
index: next,
|
|
140
|
+
total,
|
|
141
|
+
id: step?.record.id ?? null,
|
|
142
|
+
kind: step?.kind ?? null,
|
|
143
|
+
reason,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Advance once the host persists the submitted record (status leaves `open` or
|
|
148
|
+
// the record is withdrawn); a failed save keeps the person on the question.
|
|
149
|
+
$effect(() => {
|
|
150
|
+
if (!pendingKey) return;
|
|
151
|
+
const index = steps.findIndex((step) => step.key === pendingKey);
|
|
152
|
+
if (index >= 0 && steps[index].record.status === 'open') return;
|
|
153
|
+
const from = index >= 0 ? index + 1 : Math.min(cursor.index, total);
|
|
154
|
+
moveTo(firstOpenFrom(from), 'answered');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
$effect(() => {
|
|
158
|
+
if (!heading || !focusRequested) return;
|
|
159
|
+
focusRequested = false;
|
|
160
|
+
const target = heading;
|
|
161
|
+
void tick().then(() => target.focus());
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
function sortedOptions<T extends { id: string; recommended?: boolean }>(
|
|
165
|
+
options: T[] | undefined,
|
|
166
|
+
recommendedId: string | undefined,
|
|
167
|
+
reserveOther: boolean,
|
|
168
|
+
): Array<T & { isRecommended: boolean }> {
|
|
169
|
+
return (
|
|
170
|
+
(options ?? [])
|
|
171
|
+
// `other` is reserved for the question form's free-text choice only.
|
|
172
|
+
.filter((option) => !reserveOther || option.id !== 'other')
|
|
173
|
+
.map((option) => ({
|
|
174
|
+
...option,
|
|
175
|
+
isRecommended: recommendedId
|
|
176
|
+
? option.id === recommendedId
|
|
177
|
+
: Boolean(option.recommended),
|
|
178
|
+
}))
|
|
179
|
+
.sort((a, b) => Number(b.isRecommended) - Number(a.isRecommended))
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function submitAnswer(event: SubmitEvent, question: ProjectQuestionView) {
|
|
184
|
+
event.preventDefault();
|
|
185
|
+
if (busy) return;
|
|
186
|
+
const form = new FormData(event.currentTarget as HTMLFormElement);
|
|
187
|
+
const selectedOptionId = String(form.get('selectedOptionId') ?? '');
|
|
188
|
+
const structured = Boolean(question.options?.length);
|
|
189
|
+
if (structured && !selectedOptionId) return;
|
|
190
|
+
const option = question.options?.find(
|
|
191
|
+
(entry) => entry.id === selectedOptionId,
|
|
192
|
+
);
|
|
193
|
+
const answer =
|
|
194
|
+
structured && selectedOptionId !== 'other'
|
|
195
|
+
? (option?.label ?? '')
|
|
196
|
+
: String(form.get('answer') ?? '').trim();
|
|
197
|
+
if (!answer) return;
|
|
198
|
+
pendingKey = `question:${question.id}`;
|
|
199
|
+
void onanswer?.({
|
|
200
|
+
id: question.id,
|
|
201
|
+
answer,
|
|
202
|
+
expectedRevision: question.revision,
|
|
203
|
+
...(structured ? { selectedOptionId } : {}),
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function submitDecision(event: SubmitEvent, decision: ProjectDecisionView) {
|
|
208
|
+
event.preventDefault();
|
|
209
|
+
if (busy) return;
|
|
210
|
+
const form = new FormData(event.currentTarget as HTMLFormElement);
|
|
211
|
+
const outcome = String(form.get('outcome') ?? '').trim();
|
|
212
|
+
if (!outcome) return;
|
|
213
|
+
pendingKey = `decision:${decision.id}`;
|
|
214
|
+
void ondecide?.({
|
|
215
|
+
id: decision.id,
|
|
216
|
+
outcome,
|
|
217
|
+
rationale: String(form.get('rationale') ?? '').trim(),
|
|
218
|
+
expectedRevision: decision.revision,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
</script>
|
|
222
|
+
|
|
223
|
+
<section
|
|
224
|
+
class="interview"
|
|
225
|
+
aria-label={t(M['projects.interview.aria'])}
|
|
226
|
+
data-smrt-component="ProjectInterview"
|
|
227
|
+
data-motion={reducedMotion ? 'off' : 'on'}
|
|
228
|
+
>
|
|
229
|
+
{#if total === 0}
|
|
230
|
+
<p class="empty">{t(M['projects.interview.empty'])}</p>
|
|
231
|
+
{:else}
|
|
232
|
+
<div class="progress">
|
|
233
|
+
<p aria-live="polite">
|
|
234
|
+
{#if current}{t(M['projects.interview.progress'], { current: position + 1, total })} · {/if}{t(M['projects.interview.answered_count'], { answered: answeredCount, total })}
|
|
235
|
+
</p>
|
|
236
|
+
<progress value={answeredCount} max={total} aria-label={t(M['projects.interview.answered_count'], { answered: answeredCount, total })}></progress>
|
|
237
|
+
</div>
|
|
238
|
+
|
|
239
|
+
{#key current?.key ?? 'complete'}
|
|
240
|
+
<article class="step" data-interview-step={current?.key ?? 'complete'}>
|
|
241
|
+
{#if current}
|
|
242
|
+
{@const frame = framing[current.record.id] ?? {}}
|
|
243
|
+
{@const prompt = current.kind === 'question' ? current.record.prompt : current.record.question}
|
|
244
|
+
<h2 tabindex="-1" bind:this={heading}>{prompt}</h2>
|
|
245
|
+
{#if frame.why}
|
|
246
|
+
<div class="framing">
|
|
247
|
+
<h3>{t(M['projects.interview.why'])}</h3>
|
|
248
|
+
<p>{frame.why}</p>
|
|
249
|
+
</div>
|
|
250
|
+
{/if}
|
|
251
|
+
{#if frame.unblocks?.length}
|
|
252
|
+
<div class="framing">
|
|
253
|
+
<h3 id={`unblocks-${current.key}`}>{t(M['projects.interview.unblocks'])}</h3>
|
|
254
|
+
<ul aria-labelledby={`unblocks-${current.key}`}>
|
|
255
|
+
{#each frame.unblocks as label, index (`${index}:${label}`)}
|
|
256
|
+
<li>{label}</li>
|
|
257
|
+
{/each}
|
|
258
|
+
</ul>
|
|
259
|
+
</div>
|
|
260
|
+
{/if}
|
|
261
|
+
|
|
262
|
+
{#if current.record.status !== 'open'}
|
|
263
|
+
<div class="recorded">
|
|
264
|
+
<h3>{t(M['projects.interview.recorded'])}</h3>
|
|
265
|
+
{#if current.kind === 'question'}
|
|
266
|
+
<p>{current.record.answer}</p>
|
|
267
|
+
{:else}
|
|
268
|
+
{@const decision = current.record}
|
|
269
|
+
<p>
|
|
270
|
+
<strong>{decision.options?.find((option) => option.id === decision.outcome)?.label ?? decision.outcome}</strong>
|
|
271
|
+
{#if decision.rationale}<span>{decision.rationale}</span>{/if}
|
|
272
|
+
</p>
|
|
273
|
+
{/if}
|
|
274
|
+
</div>
|
|
275
|
+
{:else if !current.record.canResolve}
|
|
276
|
+
<p class="empty">{t(M['projects.interview.not_yours'])}</p>
|
|
277
|
+
{:else if current.kind === 'question'}
|
|
278
|
+
{@const question = current.record}
|
|
279
|
+
{@const options = sortedOptions(question.options, frame.recommendedOptionId, true)}
|
|
280
|
+
<Form onsubmit={(event) => submitAnswer(event, question)}>
|
|
281
|
+
{#if options.length}
|
|
282
|
+
<fieldset disabled={busy}>
|
|
283
|
+
<legend>{t(M['projects.interview.your_answer'])}</legend>
|
|
284
|
+
{#each options as option (option.id)}
|
|
285
|
+
<label class="choice" class:recommended={option.isRecommended}>
|
|
286
|
+
<Input type="radio" name="selectedOptionId" value={option.id} required interaction={false} checked={selections[current.key] === option.id} onchange={() => (selections[current.key] = option.id)} />
|
|
287
|
+
<span>{option.label}{#if option.isRecommended} <strong>{t(M['projects.governance.recommended'])}</strong>{/if}
|
|
288
|
+
{#if option.description}<small>{option.description}</small>{/if}
|
|
289
|
+
</span>
|
|
290
|
+
</label>
|
|
291
|
+
{/each}
|
|
292
|
+
<label class="choice">
|
|
293
|
+
<Input type="radio" name="selectedOptionId" value="other" required interaction={false} checked={selections[current.key] === 'other'} onchange={() => (selections[current.key] = 'other')} />
|
|
294
|
+
<span>{t(M['projects.governance.other'])}</span>
|
|
295
|
+
</label>
|
|
296
|
+
<label for={`interview-answer-${question.id}`}>{t(M['projects.governance.other_answer'])}</label>
|
|
297
|
+
<Textarea id={`interview-answer-${question.id}`} name="answer" required={selections[current.key] === 'other'}></Textarea>
|
|
298
|
+
</fieldset>
|
|
299
|
+
{:else}
|
|
300
|
+
<label for={`interview-answer-${question.id}`}>{t(M['projects.interview.your_answer'])}</label>
|
|
301
|
+
<Textarea id={`interview-answer-${question.id}`} name="answer" required disabled={busy}></Textarea>
|
|
302
|
+
{/if}
|
|
303
|
+
<Button type="submit" disabled={busy}>{t(M['projects.interview.submit'])}</Button>
|
|
304
|
+
</Form>
|
|
305
|
+
{:else}
|
|
306
|
+
{@const decision = current.record}
|
|
307
|
+
{@const options = sortedOptions(decision.options, frame.recommendedOptionId, false)}
|
|
308
|
+
<Form onsubmit={(event) => submitDecision(event, decision)}>
|
|
309
|
+
{#if options.length}
|
|
310
|
+
<fieldset disabled={busy}>
|
|
311
|
+
<legend>{t(M['projects.interview.your_choice'])}</legend>
|
|
312
|
+
{#each options as option (option.id)}
|
|
313
|
+
<label class="choice" class:recommended={option.isRecommended}>
|
|
314
|
+
<Input type="radio" name="outcome" value={option.id} required interaction={false} />
|
|
315
|
+
<span>{option.label}{#if option.isRecommended} <strong>{t(M['projects.governance.recommended'])}</strong>{/if}
|
|
316
|
+
{#if option.description}<small>{option.description}</small>{/if}
|
|
317
|
+
</span>
|
|
318
|
+
</label>
|
|
319
|
+
{/each}
|
|
320
|
+
</fieldset>
|
|
321
|
+
{:else}
|
|
322
|
+
<label for={`interview-outcome-${decision.id}`}>{t(M['projects.interview.your_choice'])}</label>
|
|
323
|
+
<Input id={`interview-outcome-${decision.id}`} name="outcome" required disabled={busy} />
|
|
324
|
+
{/if}
|
|
325
|
+
<label for={`interview-rationale-${decision.id}`}>{t(M['projects.interview.notes'])}</label>
|
|
326
|
+
<Textarea id={`interview-rationale-${decision.id}`} name="rationale" disabled={busy}></Textarea>
|
|
327
|
+
<Button type="submit" disabled={busy}>{t(M['projects.interview.submit'])}</Button>
|
|
328
|
+
</Form>
|
|
329
|
+
{/if}
|
|
330
|
+
|
|
331
|
+
<nav class="navigation" aria-label={t(M['projects.interview.navigation'])}>
|
|
332
|
+
<Button type="button" variant="ghost" disabled={busy || position === 0} onclick={() => moveTo(position - 1, 'back')}>{t(M['projects.interview.back'])}</Button>
|
|
333
|
+
{#if current.record.status === 'open'}
|
|
334
|
+
<Button type="button" variant="ghost" disabled={busy} onclick={() => moveTo(position + 1, 'skip')}>{t(M['projects.interview.skip'])}</Button>
|
|
335
|
+
{:else}
|
|
336
|
+
<Button type="button" variant="ghost" disabled={busy} onclick={() => moveTo(firstOpenFrom(position + 1), 'next')}>{t(M['projects.interview.next'])}</Button>
|
|
337
|
+
{/if}
|
|
338
|
+
</nav>
|
|
339
|
+
{:else if openCount === 0}
|
|
340
|
+
<h2 tabindex="-1" bind:this={heading}>{t(M['projects.interview.complete_title'])}</h2>
|
|
341
|
+
<p>{t(M['projects.interview.complete_description'])}</p>
|
|
342
|
+
<nav class="navigation" aria-label={t(M['projects.interview.navigation'])}>
|
|
343
|
+
<Button type="button" variant="ghost" disabled={busy} onclick={() => moveTo(total - 1, 'back')}>{t(M['projects.interview.back'])}</Button>
|
|
344
|
+
</nav>
|
|
345
|
+
{:else}
|
|
346
|
+
<h2 tabindex="-1" bind:this={heading}>{t(M['projects.interview.remaining_title'])}</h2>
|
|
347
|
+
<p>{t(M['projects.interview.remaining_description'], { count: openCount })}</p>
|
|
348
|
+
<nav class="navigation" aria-label={t(M['projects.interview.navigation'])}>
|
|
349
|
+
<Button type="button" variant="ghost" disabled={busy} onclick={() => moveTo(total - 1, 'back')}>{t(M['projects.interview.back'])}</Button>
|
|
350
|
+
<Button type="button" disabled={busy} onclick={() => moveTo(firstOpenFrom(0), 'review')}>{t(M['projects.interview.review_skipped'])}</Button>
|
|
351
|
+
</nav>
|
|
352
|
+
{/if}
|
|
353
|
+
</article>
|
|
354
|
+
{/key}
|
|
355
|
+
{/if}
|
|
356
|
+
</section>
|
|
357
|
+
|
|
358
|
+
<style>
|
|
359
|
+
@layer smrt.reset, smrt.structure, smrt.theme;
|
|
360
|
+
@layer smrt.theme {
|
|
361
|
+
.interview { display: grid; gap: var(--smrt-spacing-4); max-width: 40rem; }
|
|
362
|
+
.progress { display: grid; gap: var(--smrt-spacing-2); }
|
|
363
|
+
.progress p, .step h2, .step h3, .step p { margin: 0; }
|
|
364
|
+
.progress p, .empty, .choice small { color: var(--smrt-color-on-surface-variant); }
|
|
365
|
+
.progress progress { width: 100%; }
|
|
366
|
+
.step { border: var(--smrt-hairline); border-radius: var(--smrt-radius-medium); display: grid; gap: var(--smrt-spacing-4); padding: var(--smrt-spacing-5); }
|
|
367
|
+
.step h2:focus { outline: none; }
|
|
368
|
+
.step h2:focus-visible { outline: var(--smrt-focus-ring-width) solid var(--smrt-focus-ring-color); outline-offset: var(--smrt-focus-ring-offset); }
|
|
369
|
+
.framing { display: grid; gap: var(--smrt-spacing-1); }
|
|
370
|
+
.framing h3, .recorded h3 { font-size: var(--smrt-typography-label-large-size); }
|
|
371
|
+
.framing ul { margin: 0; padding-inline-start: var(--smrt-spacing-5); }
|
|
372
|
+
.recorded p { display: grid; gap: var(--smrt-spacing-1); }
|
|
373
|
+
.step :global(form) { display: grid; gap: var(--smrt-spacing-3); }
|
|
374
|
+
.step :global(form button[type='submit']) { justify-self: start; }
|
|
375
|
+
fieldset { display: grid; gap: var(--smrt-spacing-2); border: 0; padding: 0; margin: 0; min-width: 0; }
|
|
376
|
+
legend { margin-bottom: var(--smrt-spacing-2); padding: 0; }
|
|
377
|
+
.choice { display: flex; align-items: start; gap: var(--smrt-spacing-2); }
|
|
378
|
+
.choice.recommended { font-weight: var(--smrt-typography-weight-medium, 500); }
|
|
379
|
+
.choice :global(input[type='radio']) { width: auto; min-height: auto; padding: 0; }
|
|
380
|
+
.choice small { display: block; }
|
|
381
|
+
.navigation { display: flex; flex-wrap: wrap; gap: var(--smrt-spacing-2); justify-content: space-between; }
|
|
382
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
383
|
+
.interview[data-motion='on'] .step { animation: smrt-interview-enter 160ms ease-out; }
|
|
384
|
+
}
|
|
385
|
+
@keyframes smrt-interview-enter { from { opacity: 0; transform: translateY(0.25rem); } to { opacity: 1; transform: none; } }
|
|
386
|
+
}
|
|
387
|
+
</style>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ProjectDecisionResolveInput, ProjectDecisionView, ProjectInterviewAdvanceDetail, ProjectInterviewFraming, ProjectQuestionAnswerInput, ProjectQuestionView } from './project-governance-types.js';
|
|
2
|
+
export interface Props {
|
|
3
|
+
/** Authorized questions, in the order the host wants them asked. */
|
|
4
|
+
questions?: ProjectQuestionView[];
|
|
5
|
+
/** Authorized decisions, asked after the questions in the host's order. */
|
|
6
|
+
decisions?: ProjectDecisionView[];
|
|
7
|
+
/** Disable submissions and navigation while the host saves a change. */
|
|
8
|
+
busy?: boolean;
|
|
9
|
+
/** Plain-language framing keyed by question or decision id. */
|
|
10
|
+
framing?: Record<string, ProjectInterviewFraming>;
|
|
11
|
+
/**
|
|
12
|
+
* Zero-based record to open first (e.g. a resumed position). Defaults to the
|
|
13
|
+
* first open record; read once on mount.
|
|
14
|
+
*/
|
|
15
|
+
startIndex?: number;
|
|
16
|
+
/** Save an explicit answer with the observed revision. */
|
|
17
|
+
onanswer?: (input: ProjectQuestionAnswerInput) => void | Promise<void>;
|
|
18
|
+
/** Save a selected outcome and optional rationale with the observed revision. */
|
|
19
|
+
ondecide?: (input: ProjectDecisionResolveInput) => void | Promise<void>;
|
|
20
|
+
/** Called whenever the interview moves to another record or completes. */
|
|
21
|
+
onadvance?: (detail: ProjectInterviewAdvanceDetail) => void;
|
|
22
|
+
}
|
|
23
|
+
declare const ProjectInterview: import("svelte").Component<Props, {}, "">;
|
|
24
|
+
type ProjectInterview = ReturnType<typeof ProjectInterview>;
|
|
25
|
+
export default ProjectInterview;
|
|
26
|
+
//# sourceMappingURL=ProjectInterview.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProjectInterview.svelte.d.ts","sourceRoot":"","sources":["../../src/svelte/ProjectInterview.svelte.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,2BAA2B,EAC3B,mBAAmB,EACnB,6BAA6B,EAC7B,uBAAuB,EACvB,0BAA0B,EAC1B,mBAAmB,EACpB,MAAM,+BAA+B,CAAC;AAGvC,MAAM,WAAW,KAAK;IACpB,oEAAoE;IACpE,SAAS,CAAC,EAAE,mBAAmB,EAAE,CAAC;IAClC,2EAA2E;IAC3E,SAAS,CAAC,EAAE,mBAAmB,EAAE,CAAC;IAClC,wEAAwE;IACxE,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,+DAA+D;IAC/D,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAC;IAClD;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,0BAA0B,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvE,iFAAiF;IACjF,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,2BAA2B,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxE,0EAA0E;IAC1E,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,6BAA6B,KAAK,IAAI,CAAC;CAC7D;AA0UD,QAAA,MAAM,gBAAgB,2CAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
|
package/dist/svelte/i18n.d.ts
CHANGED
|
@@ -18,6 +18,27 @@ export declare const M: {
|
|
|
18
18
|
readonly 'projects.governance.choose_option': "projects.governance.choose_option";
|
|
19
19
|
readonly 'projects.governance.rationale': "projects.governance.rationale";
|
|
20
20
|
readonly 'projects.governance.record_decision': "projects.governance.record_decision";
|
|
21
|
+
readonly 'projects.interview.aria': "projects.interview.aria";
|
|
22
|
+
readonly 'projects.interview.progress': "projects.interview.progress";
|
|
23
|
+
readonly 'projects.interview.answered_count': "projects.interview.answered_count";
|
|
24
|
+
readonly 'projects.interview.why': "projects.interview.why";
|
|
25
|
+
readonly 'projects.interview.unblocks': "projects.interview.unblocks";
|
|
26
|
+
readonly 'projects.interview.your_answer': "projects.interview.your_answer";
|
|
27
|
+
readonly 'projects.interview.your_choice': "projects.interview.your_choice";
|
|
28
|
+
readonly 'projects.interview.notes': "projects.interview.notes";
|
|
29
|
+
readonly 'projects.interview.submit': "projects.interview.submit";
|
|
30
|
+
readonly 'projects.interview.navigation': "projects.interview.navigation";
|
|
31
|
+
readonly 'projects.interview.back': "projects.interview.back";
|
|
32
|
+
readonly 'projects.interview.skip': "projects.interview.skip";
|
|
33
|
+
readonly 'projects.interview.next': "projects.interview.next";
|
|
34
|
+
readonly 'projects.interview.recorded': "projects.interview.recorded";
|
|
35
|
+
readonly 'projects.interview.not_yours': "projects.interview.not_yours";
|
|
36
|
+
readonly 'projects.interview.empty': "projects.interview.empty";
|
|
37
|
+
readonly 'projects.interview.complete_title': "projects.interview.complete_title";
|
|
38
|
+
readonly 'projects.interview.complete_description': "projects.interview.complete_description";
|
|
39
|
+
readonly 'projects.interview.remaining_title': "projects.interview.remaining_title";
|
|
40
|
+
readonly 'projects.interview.remaining_description': "projects.interview.remaining_description";
|
|
41
|
+
readonly 'projects.interview.review_skipped': "projects.interview.review_skipped";
|
|
21
42
|
readonly 'projects.development_request.type': "projects.development_request.type";
|
|
22
43
|
readonly 'projects.development_request.feature': "projects.development_request.feature";
|
|
23
44
|
readonly 'projects.development_request.bug': "projects.development_request.bug";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../../src/svelte/i18n.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,CAAC
|
|
1
|
+
{"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../../src/svelte/i18n.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsFZ,CAAC"}
|
package/dist/svelte/i18n.js
CHANGED
|
@@ -19,6 +19,27 @@ export const M = defineMessages({
|
|
|
19
19
|
'projects.governance.choose_option': 'Select an option',
|
|
20
20
|
'projects.governance.rationale': 'Rationale',
|
|
21
21
|
'projects.governance.record_decision': 'Record decision',
|
|
22
|
+
'projects.interview.aria': 'Project interview',
|
|
23
|
+
'projects.interview.progress': 'Question {current} of {total}',
|
|
24
|
+
'projects.interview.answered_count': '{answered} of {total} answered',
|
|
25
|
+
'projects.interview.why': 'Why this matters',
|
|
26
|
+
'projects.interview.unblocks': 'What this unblocks',
|
|
27
|
+
'projects.interview.your_answer': 'Your answer',
|
|
28
|
+
'projects.interview.your_choice': 'Your choice',
|
|
29
|
+
'projects.interview.notes': 'Anything to add? (optional)',
|
|
30
|
+
'projects.interview.submit': 'Save and continue',
|
|
31
|
+
'projects.interview.navigation': 'Interview navigation',
|
|
32
|
+
'projects.interview.back': 'Back',
|
|
33
|
+
'projects.interview.skip': 'Skip for now',
|
|
34
|
+
'projects.interview.next': 'Next',
|
|
35
|
+
'projects.interview.recorded': 'Recorded answer',
|
|
36
|
+
'projects.interview.not_yours': 'Someone else on the project answers this one.',
|
|
37
|
+
'projects.interview.empty': 'There is nothing to answer right now.',
|
|
38
|
+
'projects.interview.complete_title': 'All done',
|
|
39
|
+
'projects.interview.complete_description': 'Thank you. Every question has an answer.',
|
|
40
|
+
'projects.interview.remaining_title': 'Almost there',
|
|
41
|
+
'projects.interview.remaining_description': '{count} skipped and still waiting for an answer.',
|
|
42
|
+
'projects.interview.review_skipped': 'Answer skipped questions',
|
|
22
43
|
'projects.development_request.type': 'Request type',
|
|
23
44
|
'projects.development_request.feature': 'Feature',
|
|
24
45
|
'projects.development_request.bug': 'Bug',
|
package/dist/svelte/index.d.ts
CHANGED
|
@@ -19,9 +19,10 @@ import DevelopmentBoard from './DevelopmentBoard.svelte';
|
|
|
19
19
|
import DevelopmentRequestDetail from './DevelopmentRequestDetail.svelte';
|
|
20
20
|
import PreviewApprovalPanel from './PreviewApprovalPanel.svelte';
|
|
21
21
|
import ProjectBoard from './ProjectBoard.svelte';
|
|
22
|
+
import ProjectInterview from './ProjectInterview.svelte';
|
|
22
23
|
import ProjectQuestionsAndDecisions from './ProjectQuestionsAndDecisions.svelte';
|
|
23
24
|
import ServiceEvidenceList from './ServiceEvidenceList.svelte';
|
|
24
|
-
export { ApprovalActions, AssistanceLauncher, BulkActions, DeliveryStatus, DevelopmentBoard, DevelopmentRequestDetail, DevelopmentRequestForm, DevelopmentRequestList, DurationDisplay, PreviewApprovalPanel, ProjectBoard, ProjectQuestionsAndDecisions, RejectDialog, ServiceEvidenceList, TimeEntryCard, TimeEntryList, TimeSummary, };
|
|
25
|
+
export { ApprovalActions, AssistanceLauncher, BulkActions, DeliveryStatus, DevelopmentBoard, DevelopmentRequestDetail, DevelopmentRequestForm, DevelopmentRequestList, DurationDisplay, PreviewApprovalPanel, ProjectBoard, ProjectInterview, ProjectQuestionsAndDecisions, RejectDialog, ServiceEvidenceList, TimeEntryCard, TimeEntryList, TimeSummary, };
|
|
25
26
|
export type ApprovalActionsProps = ComponentProps<typeof ApprovalActions>;
|
|
26
27
|
export type AssistanceLauncherProps = ComponentProps<typeof AssistanceLauncher>;
|
|
27
28
|
export type BulkActionsProps = ComponentProps<typeof BulkActions>;
|
|
@@ -33,11 +34,12 @@ export type DevelopmentRequestListProps = ComponentProps<typeof DevelopmentReque
|
|
|
33
34
|
export type RejectDialogProps = ComponentProps<typeof RejectDialog>;
|
|
34
35
|
export type PreviewApprovalPanelProps = ComponentProps<typeof PreviewApprovalPanel>;
|
|
35
36
|
export type ProjectBoardProps = ComponentProps<typeof ProjectBoard>;
|
|
37
|
+
export type ProjectInterviewProps = ComponentProps<typeof ProjectInterview>;
|
|
36
38
|
export type ProjectQuestionsAndDecisionsProps = ComponentProps<typeof ProjectQuestionsAndDecisions>;
|
|
37
39
|
export type ServiceEvidenceListProps = ComponentProps<typeof ServiceEvidenceList>;
|
|
38
40
|
export type { DurationDisplayProps, TimeEntryCardProps, TimeEntryListProps, TimeSummaryProps, } from '@happyvertical/smrt-timesheets/svelte';
|
|
39
41
|
export * from './delivery-types.js';
|
|
40
42
|
export type { ProjectBoardMoveIntent } from './project-board-types.js';
|
|
41
|
-
export type { ProjectDecisionResolveInput, ProjectDecisionView, ProjectQuestionAnswerInput, ProjectQuestionView, } from './project-governance-types.js';
|
|
43
|
+
export type { ProjectDecisionResolveInput, ProjectDecisionView, ProjectInterviewAdvanceDetail, ProjectInterviewFraming, ProjectInterviewRecordKind, ProjectQuestionAnswerInput, ProjectQuestionView, } from './project-governance-types.js';
|
|
42
44
|
export { type ApprovalStatus, type Currency, formatCurrency, formatDate, formatHours, formatHoursHHMM, statusColors, type TimeEntry, type TimeEntryStatus, } from './utils.js';
|
|
43
45
|
//# sourceMappingURL=index.d.ts.map
|