nucleus-core-ts 0.10.129 → 0.10.130

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 (36) hide show
  1. package/dist/.build-ok +1 -1
  2. package/dist/fe/components/VerificationFlowPage/components/VerificationFlowPage.d.ts +1 -1
  3. package/dist/fe/components/VerificationFlowPage/components/VerificationFlowPage.js +246 -3
  4. package/dist/fe/components/VerificationFlowPage/index.d.ts +7 -0
  5. package/dist/fe/components/VerificationFlowPage/index.js +6 -0
  6. package/dist/fe/components/VerificationFlowPage/labels.d.ts +149 -0
  7. package/dist/fe/components/VerificationFlowPage/labels.js +126 -1
  8. package/dist/fe/components/VerificationFlowPage/model2/ActionRows.d.ts +37 -0
  9. package/dist/fe/components/VerificationFlowPage/model2/ActionRows.js +721 -0
  10. package/dist/fe/components/VerificationFlowPage/model2/AssigneeRows.d.ts +61 -0
  11. package/dist/fe/components/VerificationFlowPage/model2/AssigneeRows.js +324 -0
  12. package/dist/fe/components/VerificationFlowPage/model2/FlowDiagram.d.ts +13 -0
  13. package/dist/fe/components/VerificationFlowPage/model2/FlowDiagram.js +637 -0
  14. package/dist/fe/components/VerificationFlowPage/model2/OutcomeRows.d.ts +13 -0
  15. package/dist/fe/components/VerificationFlowPage/model2/OutcomeRows.js +261 -0
  16. package/dist/fe/components/VerificationFlowPage/model2/ReadinessCard.d.ts +30 -0
  17. package/dist/fe/components/VerificationFlowPage/model2/ReadinessCard.js +310 -0
  18. package/dist/fe/components/VerificationFlowPage/model2/StepCard.d.ts +37 -0
  19. package/dist/fe/components/VerificationFlowPage/model2/StepCard.js +273 -0
  20. package/dist/fe/components/VerificationFlowPage/model2/StepListEditor.d.ts +44 -0
  21. package/dist/fe/components/VerificationFlowPage/model2/StepListEditor.js +344 -0
  22. package/dist/fe/components/VerificationFlowPage/model2/editorGraph.d.ts +76 -0
  23. package/dist/fe/components/VerificationFlowPage/model2/editorGraph.js +385 -0
  24. package/dist/fe/components/VerificationFlowPage/model2/index.d.ts +9 -0
  25. package/dist/fe/components/VerificationFlowPage/model2/index.js +9 -0
  26. package/dist/fe/components/VerificationFlowPage/model2/theme.d.ts +156 -0
  27. package/dist/fe/components/VerificationFlowPage/model2/theme.js +312 -0
  28. package/dist/fe/components/VerificationFlowPage/store/index.d.ts +18 -0
  29. package/dist/fe/components/VerificationFlowPage/store/index.js +54 -0
  30. package/dist/fe/components/VerificationFlowPage/types/index.d.ts +47 -1
  31. package/dist/fe/index.d.ts +2 -2
  32. package/dist/fe/index.js +1 -1
  33. package/dist/index.js +3 -3
  34. package/dist/server.js +3 -3
  35. package/dist/src/Services/Verification/model2Validate.d.ts +1 -1
  36. package/package.json +1 -1
package/dist/.build-ok CHANGED
@@ -1 +1 @@
1
- 0.10.129
1
+ 0.10.130
@@ -1,4 +1,4 @@
1
1
  import '@xyflow/react/dist/style.css';
2
2
  import type { ReactElement } from 'react';
3
3
  import type { VerificationFlowPageProps } from '../types';
4
- export declare function VerificationFlowPage({ entityName, title, subtitle, flowListAction, flowGetAction, flowSaveAction, flowPublishAction, flowUnpublishAction, flowDeleteAction, pendingAction, decideAction, uploadSignatureAction, listUsersAction, listRolesAction, entityStatusesAction, onFlowSelected, onDecisionMade, onEntityClick, onBack, showPending, showEntityStatuses, className, themeMode, labels, describeEntity, entityHref, availableChannels, initialTab, cancelAction, }: VerificationFlowPageProps): ReactElement;
4
+ export declare function VerificationFlowPage({ entityName, title, subtitle, flowListAction, flowGetAction, flowSaveAction, flowPublishAction, flowUnpublishAction, flowDeleteAction, pendingAction, decideAction, uploadSignatureAction, listUsersAction, listRolesAction, entityStatusesAction, onFlowSelected, onDecisionMade, onEntityClick, onBack, showPending, showEntityStatuses, className, themeMode, labels, describeEntity, entityHref, availableChannels, initialTab, cancelAction, authoringModel, flowFeasibilityAction, manageRolesHref, }: VerificationFlowPageProps): ReactElement;
@@ -5,7 +5,10 @@ import '@xyflow/react/dist/style.css';
5
5
  import { useEffect, useEffectEvent, useRef, useState } from 'react';
6
6
  import { cn } from '../../../utils/cn';
7
7
  import { newId } from '../../../utils/newId';
8
- import { DEFAULT_VERIFICATION_FLOW_LABELS, setActiveLabels } from '../labels';
8
+ import { DEFAULT_VERIFICATION_FLOW_LABELS, notificationChannelLabels, notificationTriggerLabels, recipientTypeLabels, setActiveLabels } from '../labels';
9
+ import { emptyModel2Graph } from '../model2/editorGraph';
10
+ import { StepListEditor } from '../model2/StepListEditor';
11
+ import { getModel2Theme } from '../model2/theme';
9
12
  import { assignStepOrders } from '../stepOrder';
10
13
  import { useVerificationFlowStore } from '../store';
11
14
  import { getVerificationFlowTheme } from '../theme';
@@ -281,7 +284,7 @@ function buildSavePayload(flowId, entityName, flowName, flowDescription, trigger
281
284
  };
282
285
  }
283
286
  // ─── Main Component ───────────────────────────────────────────────
284
- export function VerificationFlowPage({ entityName, title, subtitle, flowListAction, flowGetAction, flowSaveAction, flowPublishAction, flowUnpublishAction, flowDeleteAction, pendingAction, decideAction, uploadSignatureAction, listUsersAction, listRolesAction, entityStatusesAction, onFlowSelected, onDecisionMade, onEntityClick, onBack, showPending = true, showEntityStatuses = false, className, themeMode = 'dark', labels, describeEntity, entityHref, availableChannels, initialTab, cancelAction }) {
287
+ export function VerificationFlowPage({ entityName, title, subtitle, flowListAction, flowGetAction, flowSaveAction, flowPublishAction, flowUnpublishAction, flowDeleteAction, pendingAction, decideAction, uploadSignatureAction, listUsersAction, listRolesAction, entityStatusesAction, onFlowSelected, onDecisionMade, onEntityClick, onBack, showPending = true, showEntityStatuses = false, className, themeMode = 'dark', labels, describeEntity, entityHref, availableChannels, initialTab, cancelAction, authoringModel = 1, flowFeasibilityAction, manageRolesHref }) {
285
288
  const theme = getVerificationFlowTheme(themeMode);
286
289
  setActiveTheme(theme);
287
290
  const say = {
@@ -304,6 +307,15 @@ export function VerificationFlowPage({ entityName, title, subtitle, flowListActi
304
307
  const [showCreateForm, setShowCreateForm] = useState(false);
305
308
  const reactFlowWrapper = useRef(null);
306
309
  const reactFlowInstance = useRef(null);
310
+ /*
311
+ * Which editor is on screen — NOT which one the host prefers.
312
+ *
313
+ * An existing flow always opens in the editor it was written in, decided by
314
+ * the `model_version` the server sends back, because opening a drawn flow in
315
+ * the list editor would show an empty chain for a flow that has three steps.
316
+ * `authoringModel` only decides what the CREATE button makes.
317
+ */ const [isModel2, setIsModel2] = useState(false);
318
+ const m2Theme = getModel2Theme(themeMode);
307
319
  const resolvedTitle = title || say.pageTitle(entityName);
308
320
  const resolvedSubtitle = subtitle || entityName;
309
321
  // ─── Node/Edge Change Handlers ────────────────────────────────
@@ -400,6 +412,54 @@ export function VerificationFlowPage({ entityName, title, subtitle, flowListActi
400
412
  }
401
413
  const name = flowName || say.defaultFlowName(entityName);
402
414
  store.setIsSaving(true);
415
+ /*
416
+ * An authored chain sends a DIFFERENT body, and `graph` goes out empty
417
+ * beside it. The server reads `model2` only when `model_version` is 2, and
418
+ * `graph` only when it is not — sending both filled would leave the
419
+ * question of which one is the flow to whichever reader ran last.
420
+ */ if (isModel2) {
421
+ flowSaveAction.start({
422
+ payload: {
423
+ flow_id: flowId,
424
+ entity_name: entityName,
425
+ name,
426
+ description: flowDescription || undefined,
427
+ trigger_on: flowTriggerOn,
428
+ is_draft: true,
429
+ model_version: 2,
430
+ max_passes: store.maxPasses,
431
+ model2: store.model2Graph,
432
+ graph: {
433
+ steps: [],
434
+ edges: [],
435
+ verifier_configs: [],
436
+ notification_rules: [],
437
+ notification_recipients: [],
438
+ notification_channels: []
439
+ }
440
+ },
441
+ onAfterHandle: (res)=>{
442
+ const resObj = res;
443
+ store.setIsSaving(false);
444
+ if (resObj.success === false) {
445
+ // Refused. The chain on screen was NOT written, so the editor stays
446
+ // dirty — clearing it here would tell the author their work is saved.
447
+ store.setError(resObj.message || say.saveRefused);
448
+ return;
449
+ }
450
+ store.setError(null);
451
+ if (resObj.flow_id) store.setSelectedFlowId(resObj.flow_id);
452
+ store.setIsDirty(false);
453
+ loadFlows();
454
+ },
455
+ onErrorHandle: (err)=>{
456
+ store.setIsSaving(false);
457
+ const e = err;
458
+ store.setError(e?.message || say.saveRefused);
459
+ }
460
+ });
461
+ return;
462
+ }
403
463
  const viewport = reactFlowInstance.current ? reactFlowInstance.current.getViewport() : undefined;
404
464
  const payload = buildSavePayload(flowId, entityName, name, flowDescription, flowTriggerOn, true, viewport, nodes, edges);
405
465
  flowSaveAction.start({
@@ -492,6 +552,56 @@ export function VerificationFlowPage({ entityName, title, subtitle, flowListActi
492
552
  }
493
553
  });
494
554
  });
555
+ // ─── The authored chain ───────────────────────────────────────
556
+ /*
557
+ * Asked of the graph on screen, not of the stored flow.
558
+ *
559
+ * The moment the answer is worth anything is while the author is still
560
+ * editing. An endpoint that only answered for saved flows would mean saving a
561
+ * flow you already suspect is broken in order to find out whether it is.
562
+ */ const checkFeasibility = useEffectEvent(()=>{
563
+ if (!flowFeasibilityAction) return;
564
+ if ((store.model2Graph.steps ?? []).length === 0) {
565
+ store.setFeasibility(null);
566
+ return;
567
+ }
568
+ store.setFeasibilityLoading(true);
569
+ store.setFeasibilityError(null);
570
+ flowFeasibilityAction.start({
571
+ payload: {
572
+ entity_name: entityName,
573
+ max_passes: store.maxPasses,
574
+ model2: store.model2Graph
575
+ },
576
+ onAfterHandle: (res)=>{
577
+ store.setFeasibilityLoading(false);
578
+ const data = res?.data;
579
+ store.setFeasibility(data ?? null);
580
+ },
581
+ onErrorHandle: (err)=>{
582
+ store.setFeasibilityLoading(false);
583
+ const e = err;
584
+ /*
585
+ * The check failing is not the same as the flow being fine. Reported as
586
+ * its own line rather than left as a stale green card — a readiness
587
+ * panel that keeps showing the last good answer after it stopped being
588
+ * able to ask is worse than one that says it could not ask.
589
+ */ store.setFeasibilityError(e?.message || say.readinessSaveRefusal);
590
+ store.setFeasibility(null);
591
+ }
592
+ });
593
+ });
594
+ const handleModel2Change = useEffectEvent((next, notes)=>{
595
+ store.setModel2Graph(next);
596
+ if (notes.length > 0) store.setModel2Notes(notes);
597
+ store.setIsDirty(true);
598
+ /*
599
+ * The readiness answer is about a graph that no longer exists the moment an
600
+ * edit lands, so it is dropped rather than left on screen. Re-asking on
601
+ * every keystroke would be a request per character; the author asks again
602
+ * with the button, and a save always re-asks on the server anyway.
603
+ */ store.setFeasibility(null);
604
+ });
495
605
  // ─── View Mode Handlers ──────────────────────────────────────
496
606
  const openDesigner = useEffectEvent((flowId)=>{
497
607
  store.setSelectedFlowId(flowId);
@@ -508,6 +618,11 @@ export function VerificationFlowPage({ entityName, title, subtitle, flowListActi
508
618
  store.setSelectedFlowId(null);
509
619
  setNodes([]);
510
620
  setEdges([]);
621
+ setIsModel2(authoringModel === 2);
622
+ store.setModel2Graph(emptyModel2Graph());
623
+ store.clearModel2Notes();
624
+ store.setFeasibility(null);
625
+ store.setMaxPasses(3);
511
626
  store.setIsDirty(true);
512
627
  setShowCreateForm(false);
513
628
  setViewMode('designer');
@@ -554,6 +669,24 @@ export function VerificationFlowPage({ entityName, title, subtitle, flowListActi
554
669
  setFlowDescription(flow.description || '');
555
670
  setFlowTriggerOn(flow.trigger_on || 'create');
556
671
  }
672
+ /*
673
+ * The STORED model decides which editor opens, not the host's
674
+ * preference. A model-2 flow read into the drawn editor would render an
675
+ * empty canvas for a chain that has three steps, and the author's next
676
+ * save would write that emptiness back.
677
+ */ const model2 = resObj.data?.model2;
678
+ if (model2) {
679
+ setIsModel2(true);
680
+ store.setModel2Graph(model2);
681
+ store.setMaxPasses(Number(flow?.max_passes ?? 3));
682
+ store.setFeasibility(null);
683
+ store.clearModel2Notes();
684
+ setNodes([]);
685
+ setEdges([]);
686
+ store.setLoadingGraph(false);
687
+ return;
688
+ }
689
+ setIsModel2(false);
557
690
  const graph = resObj.data?.graph;
558
691
  if (graph?.steps && graph.steps.length > 0) {
559
692
  store.setGraphData(graph);
@@ -643,6 +776,19 @@ export function VerificationFlowPage({ entityName, title, subtitle, flowListActi
643
776
  }, [
644
777
  store.selectedFlowId
645
778
  ]);
779
+ /*
780
+ * Roles and people are loaded when the AUTHORED editor opens, not on mount.
781
+ *
782
+ * On mount would mean two directory requests on every visit to a screen most
783
+ * people open to look at the pending queue, and the pickers those lists feed
784
+ * do not exist unless the list editor is on screen. Loaded once per opening
785
+ * rather than per step card — see `loadDirectories`.
786
+ */ useEffect(()=>{
787
+ if (viewMode === 'designer' && isModel2) loadDirectories();
788
+ }, [
789
+ viewMode,
790
+ isModel2
791
+ ]);
646
792
  // ─── Derived State ────────────────────────────────────────────
647
793
  const selectedFlow = store.flows.find((f)=>f.id === store.selectedFlowId);
648
794
  const selectedNode = store.selectedNodeId ? nodes.find((n)=>n.id === store.selectedNodeId) || null : null;
@@ -675,6 +821,73 @@ export function VerificationFlowPage({ entityName, title, subtitle, flowListActi
675
821
  delete: say.triggerOnDelete,
676
822
  manual: say.triggerOnManual
677
823
  };
824
+ /*
825
+ * The enum → label maps the authored editor needs, built from the same
826
+ * functions the drawn editor's panel uses. Two maps would be two spellings of
827
+ * `on_step_reached` on two screens of one product.
828
+ */ const notifTriggerLabels = notificationTriggerLabels(say);
829
+ const recipientLabels = recipientTypeLabels(say);
830
+ const channelLabelMap = notificationChannelLabels(say);
831
+ /*
832
+ * Roles and people, once, for the whole authored editor.
833
+ *
834
+ * The drawn editor's properties panel fetches its own and caches them in
835
+ * module scope, because it is mounted and unmounted as nodes are selected.
836
+ * The list editor shows every step at once, so a fetch per step card would be
837
+ * one request per step — and every assignee picker would be a separately
838
+ * paginated list of the same twenty people.
839
+ *
840
+ * A high limit rather than pagination: these pickers name reviewers, and a
841
+ * role that is on page two of the list is a role an author cannot choose.
842
+ * When an install outgrows this the readiness card is what catches it — a
843
+ * slot pointing at nothing is blocking there.
844
+ */ const [roleOptions, setRoleOptions] = useState([]);
845
+ const [userOptions, setUserOptions] = useState([]);
846
+ const loadDirectories = useEffectEvent(()=>{
847
+ if (listRolesAction) {
848
+ listRolesAction.start({
849
+ payload: {
850
+ page: 1,
851
+ limit: 200
852
+ },
853
+ onAfterHandle: (res)=>{
854
+ const dataObj = res?.data;
855
+ const items = dataObj?.items || dataObj?.data || [];
856
+ setRoleOptions(items.map((r)=>({
857
+ id: r.id,
858
+ name: r.name
859
+ })));
860
+ },
861
+ onErrorHandle: ()=>{
862
+ setRoleOptions([]);
863
+ }
864
+ });
865
+ }
866
+ if (listUsersAction) {
867
+ listUsersAction.start({
868
+ payload: {
869
+ page: 1,
870
+ limit: 200
871
+ },
872
+ onAfterHandle: (res)=>{
873
+ const dataObj = res?.data;
874
+ const items = dataObj?.items || dataObj?.data || [];
875
+ /*
876
+ * A person with no display name still has to be pickable, and by
877
+ * something a human recognises. The e-mail is that; the id is not —
878
+ * a reviewer chosen by uuid is a reviewer nobody can verify was the
879
+ * right one.
880
+ */ setUserOptions(items.map((u)=>({
881
+ id: u.id,
882
+ name: u.name || u.email || u.id
883
+ })));
884
+ },
885
+ onErrorHandle: ()=>{
886
+ setUserOptions([]);
887
+ }
888
+ });
889
+ }
890
+ });
678
891
  // ─── Render ───────────────────────────────────────────────────
679
892
  return /*#__PURE__*/ _jsxs("div", {
680
893
  className: cn(theme.layout.root, className),
@@ -1111,7 +1324,37 @@ export function VerificationFlowPage({ entityName, title, subtitle, flowListActi
1111
1324
  })
1112
1325
  ]
1113
1326
  }),
1114
- viewMode === 'designer' && store.activeTab === 'flow' && /*#__PURE__*/ _jsxs("div", {
1327
+ viewMode === 'designer' && store.activeTab === 'flow' && isModel2 && /*#__PURE__*/ _jsx(StepListEditor, {
1328
+ graph: store.model2Graph,
1329
+ maxPasses: store.maxPasses,
1330
+ notes: store.model2Notes,
1331
+ roles: roleOptions,
1332
+ users: userOptions,
1333
+ triggerLabels: notifTriggerLabels,
1334
+ recipientTypeLabels: recipientLabels,
1335
+ channelLabels: channelLabelMap,
1336
+ availableChannels: availableChannels,
1337
+ feasibility: store.feasibility,
1338
+ isCheckingFeasibility: store.isCheckingFeasibility,
1339
+ feasibilityError: store.feasibilityError,
1340
+ manageRolesHref: manageRolesHref,
1341
+ diagramOpen: store.diagramOpen,
1342
+ say: say,
1343
+ theme: m2Theme,
1344
+ themeMode: themeMode,
1345
+ disabled: false,
1346
+ newStepId: newId,
1347
+ onGraphChange: handleModel2Change,
1348
+ onMaxPassesChange: (v)=>{
1349
+ store.setMaxPasses(v);
1350
+ store.setIsDirty(true);
1351
+ },
1352
+ onDismissNotes: store.clearModel2Notes,
1353
+ onToggleDiagram: ()=>store.setDiagramOpen(!store.diagramOpen),
1354
+ onRecheckFeasibility: checkFeasibility,
1355
+ confirmRemoveStep: (step)=>typeof window === 'undefined' ? true : window.confirm(say.removeStepConfirm(step.name || say.stepNumber(step.step_order)))
1356
+ }),
1357
+ viewMode === 'designer' && store.activeTab === 'flow' && !isModel2 && /*#__PURE__*/ _jsxs("div", {
1115
1358
  className: theme.layout.main,
1116
1359
  children: [
1117
1360
  /*#__PURE__*/ _jsx("aside", {
@@ -6,6 +6,13 @@ export { VerificationEntityList, type VerificationEntityListProps, type Verifica
6
6
  export { VerificationFlowPage } from './components/VerificationFlowPage';
7
7
  export { VerificationStatusCard, type VerificationStatusCardProps, type VerificationStatusCardTheme, } from './components/VerificationStatusCard';
8
8
  export { DEFAULT_VERIFICATION_FLOW_LABELS, type VerificationFlowLabels, } from './labels';
9
+ /**
10
+ * The authored-chain editor. Exported whole, because a host that wants a
11
+ * different arrangement of the same blocks — the readiness card beside the
12
+ * list, say, or the diagram on its own screen — can only have it if the pieces
13
+ * are reachable.
14
+ */
15
+ export * from './model2';
9
16
  export { useVerificationFlowStore } from './store';
10
17
  export type { VerificationFlowPageTheme } from './theme';
11
18
  export { extendVerificationFlowPageTheme, getVerificationFlowTheme, verificationFlowPageLightTheme, verificationFlowPageTheme, } from './theme';
@@ -6,5 +6,11 @@ export { VerificationEntityList } from './components/VerificationEntityList';
6
6
  export { VerificationFlowPage } from './components/VerificationFlowPage';
7
7
  export { VerificationStatusCard } from './components/VerificationStatusCard';
8
8
  export { DEFAULT_VERIFICATION_FLOW_LABELS } from './labels';
9
+ /**
10
+ * The authored-chain editor. Exported whole, because a host that wants a
11
+ * different arrangement of the same blocks — the readiness card beside the
12
+ * list, say, or the diagram on its own screen — can only have it if the pieces
13
+ * are reachable.
14
+ */ export * from './model2';
9
15
  export { useVerificationFlowStore } from './store';
10
16
  export { extendVerificationFlowPageTheme, getVerificationFlowTheme, verificationFlowPageLightTheme, verificationFlowPageTheme } from './theme';
@@ -260,6 +260,155 @@ export type VerificationFlowLabels = {
260
260
  * 8/5/2026, which a reader in Turkey reads as the 8th of May. Naming the
261
261
  * locale makes both passes agree and both readers right.
262
262
  */
263
+ /** The editor's own heading and the one-line explanation under it. */
264
+ authoredTitle: string;
265
+ authoredSubtitle: string;
266
+ /** The button that adds a step to the end of the list. */
267
+ addStep: string;
268
+ addStepFirst: string;
269
+ /** An empty chain. */
270
+ authoredEmptyTitle: string;
271
+ authoredEmptyBody: string;
272
+ /** The step card. */
273
+ stepNumber: (order: number) => string;
274
+ stepNamePlaceholder: (order: number) => string;
275
+ stepDescriptionPlaceholder: string;
276
+ moveStepUp: string;
277
+ moveStepDown: string;
278
+ removeStep: string;
279
+ removeStepConfirm: (name: string) => string;
280
+ /** What kind of step this is. */
281
+ stepKind: string;
282
+ stepKindApproval: string;
283
+ stepKindApprovalHint: string;
284
+ stepKindRevision: string;
285
+ stepKindRevisionHint: string;
286
+ /** Who is asked. */
287
+ assignees: string;
288
+ assigneesEmpty: string;
289
+ addAssignee: string;
290
+ removeAssignee: string;
291
+ assigneeTypeRole: string;
292
+ assigneeTypeUser: string;
293
+ assigneeTypeAuthor: string;
294
+ assigneeTypeAuthorHint: string;
295
+ pickRole: string;
296
+ pickUser: string;
297
+ roleQuorum: string;
298
+ roleQuorumOne: string;
299
+ roleQuorumOneHint: string;
300
+ roleQuorumAll: string;
301
+ roleQuorumAllHint: string;
302
+ requireSignatureHint: string;
303
+ /** How many of them have to decide. */
304
+ approvalMode: string;
305
+ approvalModeAll: string;
306
+ approvalModeAllHint: string;
307
+ approvalModeAny: string;
308
+ approvalModeAnyHint: string;
309
+ /** Where each outcome sends the record. */
310
+ outcomes: string;
311
+ onApproved: string;
312
+ onApprovedNext: string;
313
+ onApprovedComplete: string;
314
+ onApprovedGoto: string;
315
+ onRejected: string;
316
+ onRejectedEnd: string;
317
+ onRejectedGoto: string;
318
+ outcomeTargetPlaceholder: string;
319
+ /** A step in a dropdown of targets. */
320
+ stepOption: (order: number, name?: string) => string;
321
+ noEarlierStep: string;
322
+ noLaterStep: string;
323
+ /** The loop. */
324
+ maxPasses: string;
325
+ maxPassesHint: string;
326
+ maxPassesValue: (n: number) => string;
327
+ /** The actions plane. */
328
+ actions: string;
329
+ flowActions: string;
330
+ flowActionsHint: string;
331
+ stepActions: string;
332
+ addAction: string;
333
+ removeAction: string;
334
+ actionsEmpty: string;
335
+ actionTrigger: string;
336
+ actionRecipients: string;
337
+ actionRecipientsEmpty: string;
338
+ addRecipient: string;
339
+ actionChannels: string;
340
+ actionEnabled: string;
341
+ actionDisabled: string;
342
+ actionFiresOnPass: string;
343
+ actionFiresEvery: string;
344
+ actionFiresFirst: string;
345
+ actionFiresRepeat: string;
346
+ actionTitleTemplate: string;
347
+ actionBodyTemplate: string;
348
+ actionWindow: string;
349
+ actionStartsAt: string;
350
+ actionExpiresAt: string;
351
+ actionTemplateHint: string;
352
+ /** The new triggers model 1 did not have. */
353
+ notifyTriggerFlowRejected: string;
354
+ notifyTriggerResubmitted: string;
355
+ /** The readiness card. */
356
+ readinessTitle: string;
357
+ readinessChecking: string;
358
+ readinessOk: string;
359
+ readinessBlocked: (count: number) => string;
360
+ readinessWarnings: (count: number) => string;
361
+ readinessRecheck: string;
362
+ readinessStepNeeds: (decisions: number, people: number) => string;
363
+ readinessChainNeeds: (needed: number, available: number) => string;
364
+ readinessSlotPeople: (count: number) => string;
365
+ readinessSlotDeferred: string;
366
+ readinessSaveRefusal: string;
367
+ /** Where to go and fix a staffing problem, when the host says there is a screen. */
368
+ readinessManageRoles: string;
369
+ /** The notes an edit produces. */
370
+ editNoteRejectTargetCleared: (stepName: string) => string;
371
+ editNoteApproveTargetCleared: (stepName: string) => string;
372
+ editNoteLastStepCompletes: (stepName: string) => string;
373
+ editNoteCompletionHandedOver: (stepName: string) => string;
374
+ editNoteActionsDropped: (count: number) => string;
375
+ editNotesDismiss: string;
376
+ /** The generated diagram. */
377
+ diagramTitle: string;
378
+ diagramHint: string;
379
+ diagramShow: string;
380
+ diagramHide: string;
381
+ /** The question the per-assignee type picker asks. Not the block title. */
382
+ assigneeTypeLabel: string;
383
+ /** Names one slot for a screen reader walking the rows. */
384
+ assigneeSlot: (index: number) => string;
385
+ /** Shown when a step has 0 or 1 assignees, where the two modes do the same thing. */
386
+ approvalModeNoEffectHint: string;
387
+ /** The per-row remove control in an action's recipient list. */
388
+ removeRecipient: string;
389
+ /** How many characters are left in a notification title. */
390
+ actionTitleRemaining: (remaining: number) => string;
391
+ /** Guidance under the every/first/repeat chips. About the ACTION, not the loop limit. */
392
+ actionFiresOnPassHint: string;
393
+ /** The readiness answer when the check itself could not be made. */
394
+ readinessUnclear: string;
395
+ /** Announced before a blocking finding, since red is not available to everyone. */
396
+ readinessBlockingLabel: string;
397
+ /** Announced before a warning. */
398
+ readinessWarningLabel: string;
399
+ /** Both counts at once, for the head line when a flow has blocking AND warnings. */
400
+ readinessBlockedAndWarned: (blocking: number, warnings: number) => string;
401
+ /** In the diagram: a step nobody is assigned to. Must be SHORT; svg text does not wrap. */
402
+ diagramNobodyDecides: string;
403
+ /** In the diagram: how many people this step asks. Must be SHORT. */
404
+ diagramAssigneeCount: (count: number) => string;
405
+ /** In the diagram's text alternative: an approval that skips ahead. */
406
+ diagramSkipsTo: (stepLabel: string) => string;
407
+ /** In the diagram's text alternative: a rejection that goes back. */
408
+ diagramSendsBackTo: (stepLabel: string) => string;
409
+ /** In the diagram's text alternative: the chain finishing approved / rejected. */
410
+ diagramFinishesApproved: string;
411
+ diagramFinishesRejected: string;
263
412
  locale: string;
264
413
  };
265
414
  export declare const DEFAULT_VERIFICATION_FLOW_LABELS: VerificationFlowLabels;
@@ -189,6 +189,124 @@
189
189
  decisionApproved: 'approved',
190
190
  decisionRejected: 'rejected',
191
191
  decisionPending: 'pending',
192
+ authoredTitle: 'Approval chain',
193
+ authoredSubtitle: 'The steps below run in order. Each one says who is asked, what closes it, and where each answer sends the record.',
194
+ addStep: 'Add step',
195
+ addStepFirst: 'Add the first step',
196
+ authoredEmptyTitle: 'No steps yet',
197
+ authoredEmptyBody: 'An approval chain is a list of steps. Add one, say who is asked to decide it, and the record will go to them in that order.',
198
+ stepNumber: (order)=>`Step ${order}`,
199
+ stepNamePlaceholder: (order)=>`Name this step (e.g. Quality review) — step ${order}`,
200
+ stepDescriptionPlaceholder: 'What the person deciding this step should check (optional)',
201
+ moveStepUp: 'Move earlier',
202
+ moveStepDown: 'Move later',
203
+ removeStep: 'Remove this step',
204
+ removeStepConfirm: (name)=>`Remove ${name}? Anything attached to it is removed with it.`,
205
+ stepKind: 'Kind',
206
+ stepKindApproval: 'Approval',
207
+ stepKindApprovalHint: 'Somebody is asked to approve or reject the record.',
208
+ stepKindRevision: 'Revision',
209
+ stepKindRevisionHint: 'Where a rejected record goes to be corrected. Send a rejection here from a later step.',
210
+ assignees: 'Who decides',
211
+ assigneesEmpty: 'Nobody yet. A step with nobody assigned stops the chain there.',
212
+ addAssignee: 'Add someone',
213
+ removeAssignee: 'Remove',
214
+ assigneeTypeRole: 'A role',
215
+ assigneeTypeUser: 'A person',
216
+ assigneeTypeAuthor: 'Whoever wrote the record',
217
+ assigneeTypeAuthorHint: 'Resolved when the step opens, so it is whoever wrote the record being reviewed.',
218
+ pickRole: 'Choose a role',
219
+ pickUser: 'Choose a person',
220
+ roleQuorum: 'How many of them',
221
+ roleQuorumOne: 'Any one member',
222
+ roleQuorumOneHint: 'The first member of the role to decide closes this slot.',
223
+ roleQuorumAll: 'Every member',
224
+ roleQuorumAllHint: 'Every active member of the role must decide. Adding a member while a review is running adds a signature it waits for.',
225
+ requireSignatureHint: 'The person deciding must attach a signature file.',
226
+ approvalMode: 'What closes this step',
227
+ approvalModeAll: 'Everyone listed',
228
+ approvalModeAllHint: 'Every person above must decide before the record moves on.',
229
+ approvalModeAny: 'Any one of them',
230
+ approvalModeAnyHint: 'The first decision closes the step, and the others are no longer asked.',
231
+ outcomes: 'Where each answer sends it',
232
+ onApproved: 'If approved',
233
+ onApprovedNext: 'Go to the next step',
234
+ onApprovedComplete: 'Finish — the record is approved',
235
+ onApprovedGoto: 'Skip ahead to…',
236
+ onRejected: 'If rejected',
237
+ onRejectedEnd: 'Finish — the record is rejected',
238
+ onRejectedGoto: 'Send it back to…',
239
+ outcomeTargetPlaceholder: 'Choose a step',
240
+ stepOption: (order, name)=>name ? `${order}. ${name}` : `Step ${order}`,
241
+ noEarlierStep: 'There is no earlier step to send it back to.',
242
+ noLaterStep: 'There is no later step to skip to.',
243
+ maxPasses: 'Rounds allowed',
244
+ maxPassesHint: 'How many times a rejection may send the record back before the chain gives up and closes it rejected.',
245
+ maxPassesValue: (n)=>`${n} round${n === 1 ? '' : 's'}`,
246
+ actions: 'What happens',
247
+ flowActions: 'When the whole review starts or ends',
248
+ flowActionsHint: 'These belong to the review itself, not to any one step.',
249
+ stepActions: 'When this step happens',
250
+ addAction: 'Add something that happens',
251
+ removeAction: 'Remove',
252
+ actionsEmpty: 'Nothing happens automatically here.',
253
+ actionTrigger: 'When',
254
+ actionRecipients: 'Tell',
255
+ actionRecipientsEmpty: 'Nobody — this will send nothing.',
256
+ addRecipient: 'Add someone to tell',
257
+ actionChannels: 'How',
258
+ actionEnabled: 'On',
259
+ actionDisabled: 'Off',
260
+ actionFiresOnPass: 'Which rounds',
261
+ actionFiresEvery: 'Every round',
262
+ actionFiresFirst: 'Only the first round',
263
+ actionFiresRepeat: 'Only after the first round',
264
+ actionTitleTemplate: 'Title',
265
+ actionBodyTemplate: 'Message',
266
+ actionWindow: 'Only between',
267
+ actionStartsAt: 'From',
268
+ actionExpiresAt: 'Until',
269
+ actionTemplateHint: 'Use {{flow_name}}, {{step_name}}, {{step_order}}, {{pass_no}}, {{decision}} and {{reason}}. Double braces — single ones are sent exactly as typed.',
270
+ notifyTriggerFlowRejected: 'The whole review ended rejected',
271
+ notifyTriggerResubmitted: 'The author sent it back for review',
272
+ readinessTitle: 'Will this work?',
273
+ readinessChecking: 'Checking against the people on this install…',
274
+ readinessOk: 'Every step can be decided by somebody.',
275
+ readinessBlocked: (count)=>`${count} thing${count === 1 ? '' : 's'} would stop a real submission.`,
276
+ readinessWarnings: (count)=>`${count} thing${count === 1 ? '' : 's'} worth a look.`,
277
+ readinessRecheck: 'Check again',
278
+ readinessStepNeeds: (decisions, people)=>`${decisions} decision${decisions === 1 ? '' : 's'} · ${people} ${people === 1 ? 'person' : 'people'} available`,
279
+ readinessChainNeeds: (needed, available)=>`This chain needs ${needed} different ${needed === 1 ? 'person' : 'people'}; ${available} ${available === 1 ? 'account' : 'accounts'} can fill its steps.`,
280
+ readinessSlotPeople: (count)=>`${count} available`,
281
+ readinessSlotDeferred: 'decided when a record arrives',
282
+ readinessSaveRefusal: 'This cannot be saved yet',
283
+ readinessManageRoles: 'Manage roles and people',
284
+ editNoteRejectTargetCleared: (stepName)=>`${stepName} no longer sends a rejection anywhere — the step it pointed at is not before it any more. Choose where it should go.`,
285
+ editNoteApproveTargetCleared: (stepName)=>`${stepName} no longer skips ahead — the step it pointed at is not after it any more.`,
286
+ editNoteLastStepCompletes: (stepName)=>`${stepName} is now the last step, so it finishes the chain.`,
287
+ editNoteCompletionHandedOver: (stepName)=>`${stepName} no longer finishes the chain; the chain continues past it.`,
288
+ editNoteActionsDropped: (count)=>`${count} thing${count === 1 ? '' : 's'} attached to the removed step ${count === 1 ? 'was' : 'were'} removed with it.`,
289
+ editNotesDismiss: 'Got it',
290
+ diagramTitle: 'The chain',
291
+ diagramHint: 'Generated from the steps above. Read-only.',
292
+ diagramShow: 'Show the chain',
293
+ diagramHide: 'Hide the chain',
294
+ assigneeTypeLabel: 'Ask',
295
+ assigneeSlot: (index)=>`Person ${index}`,
296
+ approvalModeNoEffectHint: 'With one person listed these two do the same thing. It starts to matter as soon as a second is added.',
297
+ removeRecipient: 'Stop telling this one',
298
+ actionTitleRemaining: (remaining)=>`${remaining} characters left`,
299
+ actionFiresOnPassHint: 'A rejection can send the record back for another round. This says which of those rounds this one happens in.',
300
+ readinessUnclear: 'This could not be checked just now.',
301
+ readinessBlockingLabel: 'Would stop it',
302
+ readinessWarningLabel: 'Worth a look',
303
+ readinessBlockedAndWarned: (blocking, warnings)=>`${blocking} thing${blocking === 1 ? '' : 's'} would stop a real submission, and ${warnings} more ${warnings === 1 ? 'is' : 'are'} worth a look.`,
304
+ diagramNobodyDecides: 'nobody assigned',
305
+ diagramAssigneeCount: (count)=>`${count} asked`,
306
+ diagramSkipsTo: (stepLabel)=>`then skips ahead to ${stepLabel}`,
307
+ diagramSendsBackTo: (stepLabel)=>`if rejected, goes back to ${stepLabel}`,
308
+ diagramFinishesApproved: 'then the record is approved',
309
+ diagramFinishesRejected: 'if rejected, the record is rejected and the review ends',
192
310
  locale: 'en-US'
193
311
  };
194
312
  // ─── Enum → label maps ────────────────────────────────────────────
@@ -204,7 +322,14 @@ export function notificationTriggerLabels(say) {
204
322
  on_step_reached: say.notifyTriggerStepReached,
205
323
  on_approved: say.notifyTriggerApproved,
206
324
  on_rejected: say.notifyTriggerRejected,
207
- on_flow_completed: say.notifyTriggerFlowCompleted
325
+ on_flow_completed: say.notifyTriggerFlowCompleted,
326
+ /*
327
+ * The two model 1 never had. `on_flow_completed` fires only on the
328
+ * approving path, so "reddedildi, haber ver" had no home anywhere in the
329
+ * product, and nobody could be told the author had sent a rejected record
330
+ * back.
331
+ */ on_flow_rejected: say.notifyTriggerFlowRejected,
332
+ on_resubmitted: say.notifyTriggerResubmitted
208
333
  };
209
334
  }
210
335
  export function notificationChannelLabels(say) {