@startsimpli/ui 0.4.109 → 0.4.110
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@startsimpli/ui",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.110",
|
|
4
4
|
"description": "Shared UI components package for StartSimpli applications",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -45,9 +45,9 @@
|
|
|
45
45
|
"next": "^14.0.0 || ^15.0.0 || ^16.0.0",
|
|
46
46
|
"react": "^18.0.0 || ^19.0.0",
|
|
47
47
|
"react-dom": "^18.0.0 || ^19.0.0",
|
|
48
|
-
"@startsimpli/api": "^0.5.58",
|
|
49
48
|
"@startsimpli/auth": "^0.4.35",
|
|
50
|
-
"@startsimpli/i18n": "^0.1.3"
|
|
49
|
+
"@startsimpli/i18n": "^0.1.3",
|
|
50
|
+
"@startsimpli/api": "^0.5.58"
|
|
51
51
|
},
|
|
52
52
|
"peerDependenciesMeta": {
|
|
53
53
|
"@startsimpli/api": {
|
|
@@ -223,3 +223,64 @@ describe('reviewHostOf / fieldStr', () => {
|
|
|
223
223
|
expect(fieldStr(undefined, 'x')).toBe('');
|
|
224
224
|
});
|
|
225
225
|
});
|
|
226
|
+
|
|
227
|
+
// ---- decision vocabulary (bd startsim-b313v) ----
|
|
228
|
+
//
|
|
229
|
+
// THE DEFECT THESE COVER. The three labels were string literals inside
|
|
230
|
+
// `reviewDecisions`, so every tenant's every reviewable type said the same three
|
|
231
|
+
// words. On the OGMC tenant that collided in the customer's hands: approving a
|
|
232
|
+
// TOPIC ("this subject is worth writing") and approving a DRAFT ("this piece is
|
|
233
|
+
// publishable") are different KINDS of decision, and both rendered "Approve".
|
|
234
|
+
// Her question, mid-walkthrough: "so if I go, I click approve now, does it
|
|
235
|
+
// approve all of them?"
|
|
236
|
+
|
|
237
|
+
describe('decision labels are the consumer’s to name', () => {
|
|
238
|
+
const type = {
|
|
239
|
+
id: 't',
|
|
240
|
+
key: 'topic',
|
|
241
|
+
label: 'Topic',
|
|
242
|
+
attributes: [
|
|
243
|
+
{
|
|
244
|
+
id: 'a',
|
|
245
|
+
name: 'status',
|
|
246
|
+
dataType: 'enum' as const,
|
|
247
|
+
required: false,
|
|
248
|
+
config: { choices: ['suggested', 'ready', 'written', 'rejected'] },
|
|
249
|
+
},
|
|
250
|
+
],
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
it('keeps the generic words when a consumer supplies none', () => {
|
|
254
|
+
expect(reviewDecisions(resolveReviewConfig(type)).map((d) => d.label)).toEqual([
|
|
255
|
+
'Approve',
|
|
256
|
+
'Needs work',
|
|
257
|
+
'Reject',
|
|
258
|
+
]);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('lets a consumer name what the decision is ABOUT', () => {
|
|
262
|
+
const cfg = resolveReviewConfig(type, {
|
|
263
|
+
decisionLabels: { approve: 'Approve topic', reject: 'Reject topic' },
|
|
264
|
+
});
|
|
265
|
+
const byKey = Object.fromEntries(reviewDecisions(cfg).map((d) => [d.key, d.label]));
|
|
266
|
+
expect(byKey.approve).toBe('Approve topic');
|
|
267
|
+
expect(byKey.reject).toBe('Reject topic');
|
|
268
|
+
// Unnamed decisions keep the default rather than going blank.
|
|
269
|
+
expect(byKey.needs_work).toBe('Needs work');
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('ignores a blank override instead of rendering an unlabelled button', () => {
|
|
273
|
+
const cfg = resolveReviewConfig(type, { decisionLabels: { approve: ' ' } });
|
|
274
|
+
expect(reviewDecisions(cfg).find((d) => d.key === 'approve')?.label).toBe('Approve');
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('leaves every other part of the decision alone — this is copy, not behaviour', () => {
|
|
278
|
+
const plain = reviewDecisions(resolveReviewConfig(type));
|
|
279
|
+
const named = reviewDecisions(
|
|
280
|
+
resolveReviewConfig(type, { decisionLabels: { approve: 'Approve topic' } }),
|
|
281
|
+
);
|
|
282
|
+
expect(named.map((d) => [d.key, d.status, d.verdict, d.tone, d.advance, d.shortcut])).toEqual(
|
|
283
|
+
plain.map((d) => [d.key, d.status, d.verdict, d.tone, d.advance, d.shortcut]),
|
|
284
|
+
);
|
|
285
|
+
});
|
|
286
|
+
});
|
|
@@ -169,6 +169,23 @@ export interface ReviewConfig {
|
|
|
169
169
|
sourceAttrs?: string[];
|
|
170
170
|
/** Drop the middle "Needs work" decision — a binary accept/reject flow (default false). */
|
|
171
171
|
omitNeedsWork?: boolean;
|
|
172
|
+
/**
|
|
173
|
+
* What each decision is CALLED, by decision key ('approve' | 'needs_work' |
|
|
174
|
+
* 'reject'). Anything omitted keeps the generic default.
|
|
175
|
+
*
|
|
176
|
+
* WHY THIS EXISTS (bd startsim-b313v). The labels used to be string literals
|
|
177
|
+
* in `reviewDecisions` below, so every tenant's every reviewable type rendered
|
|
178
|
+
* the same three words. On OGMC that collided in the customer's hands: a
|
|
179
|
+
* reviewer approving a TOPIC ("this subject is worth writing") and a reviewer
|
|
180
|
+
* approving a DRAFT ("this piece is publishable") both pressed a button
|
|
181
|
+
* labelled "Approve", and she asked, mid-walkthrough, "so if I go, I click
|
|
182
|
+
* approve now, does it approve all of them?"
|
|
183
|
+
*
|
|
184
|
+
* A workspace cannot answer that from here — only the consumer knows what its
|
|
185
|
+
* records ARE — so the vocabulary is the consumer's to supply. Defaults are
|
|
186
|
+
* unchanged, so nothing that does not pass this renders differently.
|
|
187
|
+
*/
|
|
188
|
+
decisionLabels?: Record<string, string>;
|
|
172
189
|
}
|
|
173
190
|
|
|
174
191
|
export interface ResolvedReview {
|
|
@@ -185,6 +202,7 @@ export interface ResolvedReview {
|
|
|
185
202
|
metaAttrs: string[];
|
|
186
203
|
sourceAttrs: string[];
|
|
187
204
|
omitNeedsWork: boolean;
|
|
205
|
+
decisionLabels: Record<string, string>;
|
|
188
206
|
}
|
|
189
207
|
|
|
190
208
|
const DEFAULT_SOURCE_ATTRS = ['source_1', 'source_2', 'source_3', 'sources'];
|
|
@@ -216,9 +234,23 @@ export function resolveReviewConfig(
|
|
|
216
234
|
// Keep source attrs the caller asked for; default to the ones this type declares.
|
|
217
235
|
sourceAttrs: (config.sourceAttrs ?? DEFAULT_SOURCE_ATTRS).filter((a) => declared.has(a)),
|
|
218
236
|
omitNeedsWork: config.omitNeedsWork ?? false,
|
|
237
|
+
decisionLabels: config.decisionLabels ?? {},
|
|
219
238
|
};
|
|
220
239
|
}
|
|
221
240
|
|
|
241
|
+
/** The default name of a decision, when the consumer supplies none. */
|
|
242
|
+
export const DEFAULT_DECISION_LABELS: Record<string, string> = {
|
|
243
|
+
approve: 'Approve',
|
|
244
|
+
needs_work: 'Needs work',
|
|
245
|
+
reject: 'Reject',
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
/** What a decision is called on THIS surface — the consumer's word, else the default. */
|
|
249
|
+
export function decisionLabel(key: string, cfg: Pick<ResolvedReview, 'decisionLabels'>): string {
|
|
250
|
+
const supplied = cfg.decisionLabels?.[key];
|
|
251
|
+
return supplied && supplied.trim() ? supplied : DEFAULT_DECISION_LABELS[key] ?? key;
|
|
252
|
+
}
|
|
253
|
+
|
|
222
254
|
// ---- the single review decision (Approve / Needs work / Reject) ----
|
|
223
255
|
//
|
|
224
256
|
// status and team_verdict are two fields with different consumers (status = the
|
|
@@ -262,7 +294,7 @@ export function reviewDecisions(r: ResolvedReview): ReviewDecision[] {
|
|
|
262
294
|
if (r.transitions.approve) {
|
|
263
295
|
out.push({
|
|
264
296
|
key: 'approve',
|
|
265
|
-
label: '
|
|
297
|
+
label: decisionLabel('approve', r),
|
|
266
298
|
status: r.transitions.approve,
|
|
267
299
|
verdict: verdictOf('good'),
|
|
268
300
|
tone: 'good',
|
|
@@ -276,7 +308,7 @@ export function reviewDecisions(r: ResolvedReview): ReviewDecision[] {
|
|
|
276
308
|
if (!r.omitNeedsWork && entry && (editVerdict || entry !== r.transitions.approve)) {
|
|
277
309
|
out.push({
|
|
278
310
|
key: 'needs_work',
|
|
279
|
-
label: '
|
|
311
|
+
label: decisionLabel('needs_work', r),
|
|
280
312
|
status: entry,
|
|
281
313
|
verdict: editVerdict,
|
|
282
314
|
tone: 'neutral',
|
|
@@ -289,7 +321,7 @@ export function reviewDecisions(r: ResolvedReview): ReviewDecision[] {
|
|
|
289
321
|
if (r.transitions.reject) {
|
|
290
322
|
out.push({
|
|
291
323
|
key: 'reject',
|
|
292
|
-
label: '
|
|
324
|
+
label: decisionLabel('reject', r),
|
|
293
325
|
status: r.transitions.reject,
|
|
294
326
|
verdict: verdictOf('bad'),
|
|
295
327
|
tone: 'bad',
|