@things-factory/worklist 10.1.26 → 10.1.28

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 (74) hide show
  1. package/approval-walk/harness.mjs +255 -0
  2. package/dist-client/pages/activity/activity-list-page.d.ts +0 -2
  3. package/dist-client/pages/activity/activity-list-page.js +7 -15
  4. package/dist-client/pages/activity/activity-list-page.js.map +1 -1
  5. package/dist-client/pages/activity/activity-model-item-list.js +2 -1
  6. package/dist-client/pages/activity/activity-model-item-list.js.map +1 -1
  7. package/dist-client/pages/activity/activity-page.d.ts +6 -0
  8. package/dist-client/pages/activity/activity-page.js +171 -131
  9. package/dist-client/pages/activity/activity-page.js.map +1 -1
  10. package/dist-client/pages/activity/starter-list-page.js +6 -11
  11. package/dist-client/pages/activity/starter-list-page.js.map +1 -1
  12. package/dist-client/pages/activity-approval/activity-approval-list-page.js +8 -12
  13. package/dist-client/pages/activity-approval/activity-approval-list-page.js.map +1 -1
  14. package/dist-client/pages/activity-approval/activity-approval-page.d.ts +7 -2
  15. package/dist-client/pages/activity-approval/activity-approval-page.js +146 -138
  16. package/dist-client/pages/activity-approval/activity-approval-page.js.map +1 -1
  17. package/dist-client/pages/activity-catalog/activity-catalog-page.d.ts +10 -0
  18. package/dist-client/pages/activity-catalog/activity-catalog-page.js +56 -35
  19. package/dist-client/pages/activity-catalog/activity-catalog-page.js.map +1 -1
  20. package/dist-client/pages/activity-instance/activity-instance-list-page.d.ts +0 -1
  21. package/dist-client/pages/activity-instance/activity-instance-list-page.js +79 -107
  22. package/dist-client/pages/activity-instance/activity-instance-list-page.js.map +1 -1
  23. package/dist-client/pages/activity-instance/activity-instance-search-page.js +3 -6
  24. package/dist-client/pages/activity-instance/activity-instance-search-page.js.map +1 -1
  25. package/dist-client/pages/activity-instance/activity-instance-start-page.d.ts +6 -0
  26. package/dist-client/pages/activity-instance/activity-instance-start-page.js +216 -157
  27. package/dist-client/pages/activity-instance/activity-instance-start-page.js.map +1 -1
  28. package/dist-client/pages/activity-stats/activity-stats-list-page.js +8 -12
  29. package/dist-client/pages/activity-stats/activity-stats-list-page.js.map +1 -1
  30. package/dist-client/pages/activity-store/activity-store-page.js +4 -11
  31. package/dist-client/pages/activity-store/activity-store-page.js.map +1 -1
  32. package/dist-client/pages/activity-supervisor/reporter-list-page.js +6 -11
  33. package/dist-client/pages/activity-supervisor/reporter-list-page.js.map +1 -1
  34. package/dist-client/pages/activity-template/activity-template-list-page.js +8 -12
  35. package/dist-client/pages/activity-template/activity-template-list-page.js.map +1 -1
  36. package/dist-client/pages/activity-thread/activity-thread-list-page.js +8 -12
  37. package/dist-client/pages/activity-thread/activity-thread-list-page.js.map +1 -1
  38. package/dist-client/pages/activity-thread/activity-thread-page.d.ts +6 -2
  39. package/dist-client/pages/activity-thread/activity-thread-page.js +96 -82
  40. package/dist-client/pages/activity-thread/activity-thread-page.js.map +1 -1
  41. package/dist-client/pages/activity-thread/activity-thread-view-page.d.ts +6 -2
  42. package/dist-client/pages/activity-thread/activity-thread-view-page.js +53 -23
  43. package/dist-client/pages/activity-thread/activity-thread-view-page.js.map +1 -1
  44. package/dist-client/pages/installable-activity/installable-activity-list-page.d.ts +0 -1
  45. package/dist-client/pages/installable-activity/installable-activity-list-page.js +7 -16
  46. package/dist-client/pages/installable-activity/installable-activity-list-page.js.map +1 -1
  47. package/dist-client/pages/todo/approval-done-list-page.d.ts +0 -2
  48. package/dist-client/pages/todo/approval-done-list-page.js +7 -15
  49. package/dist-client/pages/todo/approval-done-list-page.js.map +1 -1
  50. package/dist-client/pages/todo/approval-pending-list-page.d.ts +0 -2
  51. package/dist-client/pages/todo/approval-pending-list-page.js +5 -16
  52. package/dist-client/pages/todo/approval-pending-list-page.js.map +1 -1
  53. package/dist-client/pages/todo/done-list-calendar-page.js +4 -5
  54. package/dist-client/pages/todo/done-list-calendar-page.js.map +1 -1
  55. package/dist-client/pages/todo/done-list-page.d.ts +0 -2
  56. package/dist-client/pages/todo/done-list-page.js +7 -15
  57. package/dist-client/pages/todo/done-list-page.js.map +1 -1
  58. package/dist-client/pages/todo/draft-list-page.js +6 -11
  59. package/dist-client/pages/todo/draft-list-page.js.map +1 -1
  60. package/dist-client/pages/todo/pickable-list-page.d.ts +0 -2
  61. package/dist-client/pages/todo/pickable-list-page.js +7 -15
  62. package/dist-client/pages/todo/pickable-list-page.js.map +1 -1
  63. package/dist-client/pages/todo/todo-list-page.d.ts +0 -2
  64. package/dist-client/pages/todo/todo-list-page.js +5 -15
  65. package/dist-client/pages/todo/todo-list-page.js.map +1 -1
  66. package/dist-client/tsconfig.tsbuildinfo +1 -1
  67. package/dist-server/service/activity/decision-key-declaration.d.ts +11 -2
  68. package/dist-server/service/activity/decision-key-declaration.js +22 -1
  69. package/dist-server/service/activity/decision-key-declaration.js.map +1 -1
  70. package/dist-server/service/installable-activity/installable-activity-query.d.ts +11 -0
  71. package/dist-server/service/installable-activity/installable-activity-query.js +31 -0
  72. package/dist-server/service/installable-activity/installable-activity-query.js.map +1 -1
  73. package/dist-server/tsconfig.tsbuildinfo +1 -1
  74. package/package.json +11 -11
@@ -86,12 +86,21 @@ export declare function decisionValueKey(activityName: string, inputPath: string
86
86
  * Guide lines are keyed by position. Inserting a line shifts every later translation by one, and a
87
87
  * count of keys does not notice — change guide and its translations in the same edit.
88
88
  */
89
- export declare function activityTKey(activityName: string, part: 'label'): string;
90
- export declare function activityTKey(activityName: string, part: 'guide', index: number): string;
89
+ export declare function activityTKey(activityName: string, part: 'label' | 'readiness-notes'): string;
90
+ export declare function activityTKey(activityName: string, part: 'guide' | 'readiness-gap', index: number): string;
91
91
  /**
92
92
  * One key per guide line, **always as many as there are lines.** The screen pairs the two arrays
93
93
  * by position; a shorter key list would send the last lines out untranslated with nothing to show it.
94
94
  */
95
95
  export declare function activityGuideTKeys(activityName: string, guide: string[] | undefined | null): string[];
96
+ /**
97
+ * The same for what a module declares about its own readiness.
98
+ *
99
+ * The catalog screen draws `readiness.gaps` and `readiness.notes` as sentences, and those come out
100
+ * of a catalog that is built at boot, before anyone's locale is known. So the module keeps writing
101
+ * sentences and the screen reads a translation of them, exactly as it does for label and guide
102
+ * (ADR-0054: the sentence is chosen where `context.t` is).
103
+ */
104
+ export declare function activityReadinessGapTKeys(activityName: string, gaps: string[] | undefined | null): string[];
96
105
  export declare function assertDecisionKeysAreUsable(decisionKeys: DecisionKeyDeclaration[] | undefined, activityName?: string): void;
97
106
  export {};
@@ -5,6 +5,7 @@ exports.doorSlug = doorSlug;
5
5
  exports.decisionValueKey = decisionValueKey;
6
6
  exports.activityTKey = activityTKey;
7
7
  exports.activityGuideTKeys = activityGuideTKeys;
8
+ exports.activityReadinessGapTKeys = activityReadinessGapTKeys;
8
9
  exports.assertDecisionKeysAreUsable = assertDecisionKeysAreUsable;
9
10
  /** Roles a screen knows how to draw. Widening this is an architecture decision, not a lane's. */
10
11
  exports.DECISION_ROLES = [
@@ -69,7 +70,16 @@ function decisionValueKey(activityName, inputPath) {
69
70
  }
70
71
  function activityTKey(activityName, part, index) {
71
72
  const door = `activity.${doorSlug(activityName)}`;
72
- return part === 'guide' ? `${door}.guide.${index}` : `${door}.label`;
73
+ switch (part) {
74
+ case 'guide':
75
+ return `${door}.guide.${index}`;
76
+ case 'readiness-gap':
77
+ return `${door}.readiness-gap.${index}`;
78
+ case 'readiness-notes':
79
+ return `${door}.readiness-notes`;
80
+ default:
81
+ return `${door}.label`;
82
+ }
73
83
  }
74
84
  /**
75
85
  * One key per guide line, **always as many as there are lines.** The screen pairs the two arrays
@@ -78,6 +88,17 @@ function activityTKey(activityName, part, index) {
78
88
  function activityGuideTKeys(activityName, guide) {
79
89
  return (guide || []).map((_, index) => activityTKey(activityName, 'guide', index));
80
90
  }
91
+ /**
92
+ * The same for what a module declares about its own readiness.
93
+ *
94
+ * The catalog screen draws `readiness.gaps` and `readiness.notes` as sentences, and those come out
95
+ * of a catalog that is built at boot, before anyone's locale is known. So the module keeps writing
96
+ * sentences and the screen reads a translation of them, exactly as it does for label and guide
97
+ * (ADR-0054: the sentence is chosen where `context.t` is).
98
+ */
99
+ function activityReadinessGapTKeys(activityName, gaps) {
100
+ return (gaps || []).map((_, index) => activityTKey(activityName, 'readiness-gap', index));
101
+ }
81
102
  function assertDecisionKeysAreUsable(decisionKeys, activityName) {
82
103
  if (!decisionKeys?.length) {
83
104
  return;
@@ -1 +1 @@
1
- {"version":3,"file":"decision-key-declaration.js","sourceRoot":"","sources":["../../../server/service/activity/decision-key-declaration.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Refuses a decisionKeys declaration that cannot draw, and computes the i18n keys an activity's\n * sentences are read from.\n *\n * ── This path is read from outside ────────────────────────────────────────\n * operato-plant's tests import this file by its built path,\n * `@things-factory/worklist/dist-server/service/activity/decision-key-declaration.js`, for\n * DECISION_ROLES, doorSlug and activityTKey. Moving or renaming it breaks those tests, so treat the\n * path as public.\n *\n * Refusing a declaration that cannot draw:\n *\n * Same reasoning as search keys: a declaration that does not work should fail where it arrives, not\n * where someone opens an approval. The difference is what \"not working\" means here — nothing is\n * dropped by a save, so the failure would be a screen that quietly draws less than it was told to.\n *\n * The shape is spelled out rather than imported. ActivityDecisionKeyItem carries type-graphql\n * decorators and the test runner strips types without reading them, so importing the declared class\n * pulls type-graphql in and the test file fails to load.\n */\ntype DecisionKeyDeclaration = {\n role?: string\n inputPath?: string\n valueTKey?: string\n params?: { [name: string]: string }\n}\n\n/** Roles a screen knows how to draw. Widening this is an architecture decision, not a lane's. */\nexport const DECISION_ROLES = [\n 'headline',\n 'subject',\n 'reversibility',\n 'reason',\n 'evidence',\n 'currentState',\n 'consequence'\n] as const\n\n/**\n * The roles that may appear more than once.\n *\n * An approval can change several things at once — plant's priority change pushes some orders back\n * and moves others ahead — and those are separate consequences, not one merged sentence. Grounds\n * come in several the same way: which inspection, against which spec, decided when.\n *\n * Every other role names a single value. Two headlines would leave the screen picking one with\n * nothing to say why.\n *\n * Named consequence, not effect: TwinCommand.effect is what the source reported back after dispatch\n * — an observation, and past — while this is a prediction, and future.\n */\nexport const REPEATABLE_ROLES = ['consequence', 'evidence'] as const\n\n/**\n * The door's part of a value key — the activity's name, kebab-cased.\n *\n * ── Why computed and not declared ─────────────────────────────────────────\n * A declared key is a key table, and a key table is what a computed key exists to avoid: nobody\n * writing a new door picks a key, so none of them can be picked wrong, and a test can count every\n * key that exists rather than every key somebody remembered to register.\n *\n * It is safe to hang this on `name` because `name` cannot move — it is the key the callback is\n * found by, and renaming it makes an approval finish with nothing happening. A key derived from a\n * fixed name is fixed.\n *\n * ── The boundary rule ─────────────────────────────────────────────────────\n * Activity names come in two shapes and this repository has both: 'MES Disposition Approval' and\n * 'BatchRelease'. Splitting only on spaces would leave `batchrelease` beside `mes-disposition-\n * approval`; splitting on every capital would turn 'MES' into `m-e-s`. So the split is between a\n * lowercase or digit and a capital, and between a run of capitals and a capitalised word — the same\n * rule `secretEnvName` uses, written after HOOKSECRET and HOOK_SECRET drifted apart.\n */\nexport function doorSlug(activityName: string): string {\n return String(activityName ?? '')\n .replace(/([a-z0-9])([A-Z])/g, '$1-$2')\n .replace(/([A-Z]+)([A-Z][a-z])/g, '$1-$2')\n .replace(/[^A-Za-z0-9]+/g, '-')\n .toLowerCase()\n .replace(/^-+|-+$/g, '')\n}\n\n/**\n * The i18n key for one slot's **value sentence** — `approval.<door>.<inputPath>`.\n *\n * `inputPath` and not `role`: a door repeats `consequence`, so two slots in one door would collide\n * on role and the later sentence would silently take the earlier one's key. `inputPath` is the one\n * thing already unique within a door, and it is already written in the declaration.\n */\nexport function decisionValueKey(activityName: string, inputPath: string): string {\n return `approval.${doorSlug(activityName)}.${inputPath}`\n}\n\n/**\n * The i18n key for an activity's own sentences — `activity.<door>.label`, `activity.<door>.guide.<n>`.\n *\n * Computed from `name` for the reason decisionValueKey is: nobody picks the key, so none is picked\n * wrong, and an app's test can walk its declarations and count every key that must exist.\n *\n * The server sends the result (Activity.labelTKey, InstallableActivity.labelTKey and guideTKeys);\n * screens draw `t(labelTKey, { defaultValue: label })` and never rebuild the slug. A client file that\n * imported this one would move the whole client build under dist-client/client/.\n *\n * `description` has no key. It is the developers' English line and is not shown to the person\n * doing the work.\n *\n * Guide lines are keyed by position. Inserting a line shifts every later translation by one, and a\n * count of keys does not notice — change guide and its translations in the same edit.\n */\nexport function activityTKey(activityName: string, part: 'label'): string\nexport function activityTKey(activityName: string, part: 'guide', index: number): string\nexport function activityTKey(activityName: string, part: 'label' | 'guide', index?: number): string {\n const door = `activity.${doorSlug(activityName)}`\n return part === 'guide' ? `${door}.guide.${index}` : `${door}.label`\n}\n\n/**\n * One key per guide line, **always as many as there are lines.** The screen pairs the two arrays\n * by position; a shorter key list would send the last lines out untranslated with nothing to show it.\n */\nexport function activityGuideTKeys(activityName: string, guide: string[] | undefined | null): string[] {\n return (guide || []).map((_, index) => activityTKey(activityName, 'guide', index))\n}\n\nexport function assertDecisionKeysAreUsable(\n decisionKeys: DecisionKeyDeclaration[] | undefined,\n activityName?: string\n): void {\n if (!decisionKeys?.length) {\n return\n }\n\n const named = activityName ? `activity \"${activityName}\"` : 'this activity'\n\n const unknown = decisionKeys.filter(key => !(DECISION_ROLES as readonly string[]).includes(String(key?.role ?? '')))\n\n if (unknown.length) {\n throw new Error(\n `${named} declares decision keys with a role the approval screen does not know: ` +\n `${unknown.map(key => `\"${key?.role ?? ''}\"`).join(', ')}. ` +\n `Known roles are ${DECISION_ROLES.join(', ')}. A role outside that set draws nothing.`\n )\n }\n\n const pathless = decisionKeys.filter(key => !String(key?.inputPath ?? '').trim())\n\n if (pathless.length) {\n throw new Error(\n `${named} declares ${pathless.length} decision key(s) with no inputPath. ` +\n `A decision key names the value it draws; without a path there is nothing to read.`\n )\n }\n\n /*\n * An index or a wildcard resolves to nothing at draw time and the slot would report \"the path led\n * nowhere\" — which reads as a payload that disagrees with the declaration, when the truth is that\n * the declaration used a path language that does not exist. Refusing here says which it was.\n */\n const notPaths = decisionKeys.filter(key => /[[\\]*]/.test(String(key?.inputPath ?? '')))\n\n if (notPaths.length) {\n throw new Error(\n `${named} declares decision keys whose inputPath is not a plain dotted path: ` +\n `${notPaths.map(key => `\"${key?.inputPath ?? ''}\"`).join(', ')}. ` +\n `Dots only — no array index and no wildcard. Point at the list itself; drawing its items is ` +\n `the slot's work.`\n )\n }\n\n const repeated = DECISION_ROLES.filter(\n role =>\n !(REPEATABLE_ROLES as readonly string[]).includes(role) &&\n decisionKeys.filter(key => key?.role === role).length > 1\n )\n\n if (repeated.length) {\n throw new Error(\n `${named} declares more than one \"${repeated.join('\", \"')}\" decision key. ` +\n `Only ${REPEATABLE_ROLES.map(role => `\"${role}\"`).join(' and ')} may repeat — an approval can ` +\n `change several things at once, and grounds come in several. The screen would draw one of the ` +\n `others and could not say which.`\n )\n }\n\n /*\n * A sentence may only be built from values that were snapshotted, so every params value is an\n * inputPath and reads by the same rules. Wanting a value with no field behind it means the field\n * is missing, not that params needs a second source — the two hold each other up: without the\n * value the sentence cannot be rebuilt, and a value a sentence uses is guaranteed to stay.\n *\n * Only the right side has a rule. The names on the left belong to the sentence and are the\n * locale file's business.\n */\n const badParams: string[] = []\n\n for (const key of decisionKeys) {\n for (const [name, path] of Object.entries(key?.params || {})) {\n const said = String(path ?? '')\n\n if (!said.trim()) {\n badParams.push(`\"${name}\" (empty)`)\n } else if (/[[\\]*]/.test(said)) {\n badParams.push(`\"${name}\": \"${said}\"`)\n }\n }\n }\n\n if (badParams.length) {\n throw new Error(\n `${named} declares sentence params that are not plain dotted paths into the input: ` +\n `${badParams.join(', ')}. Every params value is an inputPath — a sentence is built only from ` +\n `values the request froze, so a value with nowhere to read it from is a missing field.`\n )\n }\n\n if (!activityName) {\n return\n }\n\n /*\n * The key is computed, so a declared one that disagrees is a typo that would draw `approval.…` on\n * the card. Checking it here rather than trusting the writer is the whole reason it is computed.\n */\n const wrongKey = decisionKeys.filter(\n key => key?.valueTKey && key.valueTKey !== decisionValueKey(activityName, String(key?.inputPath ?? ''))\n )\n\n if (wrongKey.length) {\n throw new Error(\n `${named} declares value keys that are not the computed ones: ` +\n `${wrongKey\n .map(key => `\"${key?.valueTKey}\" (expected \"${decisionValueKey(activityName, String(key?.inputPath ?? ''))}\")`)\n .join(', ')}. ` +\n `The key is approval.<door>.<inputPath> and nobody picks it — a key nobody can pick is a key ` +\n `nobody can pick wrong.`\n )\n }\n}\n"]}
1
+ {"version":3,"file":"decision-key-declaration.js","sourceRoot":"","sources":["../../../server/service/activity/decision-key-declaration.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Refuses a decisionKeys declaration that cannot draw, and computes the i18n keys an activity's\n * sentences are read from.\n *\n * ── This path is read from outside ────────────────────────────────────────\n * operato-plant's tests import this file by its built path,\n * `@things-factory/worklist/dist-server/service/activity/decision-key-declaration.js`, for\n * DECISION_ROLES, doorSlug and activityTKey. Moving or renaming it breaks those tests, so treat the\n * path as public.\n *\n * Refusing a declaration that cannot draw:\n *\n * Same reasoning as search keys: a declaration that does not work should fail where it arrives, not\n * where someone opens an approval. The difference is what \"not working\" means here — nothing is\n * dropped by a save, so the failure would be a screen that quietly draws less than it was told to.\n *\n * The shape is spelled out rather than imported. ActivityDecisionKeyItem carries type-graphql\n * decorators and the test runner strips types without reading them, so importing the declared class\n * pulls type-graphql in and the test file fails to load.\n */\ntype DecisionKeyDeclaration = {\n role?: string\n inputPath?: string\n valueTKey?: string\n params?: { [name: string]: string }\n}\n\n/** Roles a screen knows how to draw. Widening this is an architecture decision, not a lane's. */\nexport const DECISION_ROLES = [\n 'headline',\n 'subject',\n 'reversibility',\n 'reason',\n 'evidence',\n 'currentState',\n 'consequence'\n] as const\n\n/**\n * The roles that may appear more than once.\n *\n * An approval can change several things at once — plant's priority change pushes some orders back\n * and moves others ahead — and those are separate consequences, not one merged sentence. Grounds\n * come in several the same way: which inspection, against which spec, decided when.\n *\n * Every other role names a single value. Two headlines would leave the screen picking one with\n * nothing to say why.\n *\n * Named consequence, not effect: TwinCommand.effect is what the source reported back after dispatch\n * — an observation, and past — while this is a prediction, and future.\n */\nexport const REPEATABLE_ROLES = ['consequence', 'evidence'] as const\n\n/**\n * The door's part of a value key — the activity's name, kebab-cased.\n *\n * ── Why computed and not declared ─────────────────────────────────────────\n * A declared key is a key table, and a key table is what a computed key exists to avoid: nobody\n * writing a new door picks a key, so none of them can be picked wrong, and a test can count every\n * key that exists rather than every key somebody remembered to register.\n *\n * It is safe to hang this on `name` because `name` cannot move — it is the key the callback is\n * found by, and renaming it makes an approval finish with nothing happening. A key derived from a\n * fixed name is fixed.\n *\n * ── The boundary rule ─────────────────────────────────────────────────────\n * Activity names come in two shapes and this repository has both: 'MES Disposition Approval' and\n * 'BatchRelease'. Splitting only on spaces would leave `batchrelease` beside `mes-disposition-\n * approval`; splitting on every capital would turn 'MES' into `m-e-s`. So the split is between a\n * lowercase or digit and a capital, and between a run of capitals and a capitalised word — the same\n * rule `secretEnvName` uses, written after HOOKSECRET and HOOK_SECRET drifted apart.\n */\nexport function doorSlug(activityName: string): string {\n return String(activityName ?? '')\n .replace(/([a-z0-9])([A-Z])/g, '$1-$2')\n .replace(/([A-Z]+)([A-Z][a-z])/g, '$1-$2')\n .replace(/[^A-Za-z0-9]+/g, '-')\n .toLowerCase()\n .replace(/^-+|-+$/g, '')\n}\n\n/**\n * The i18n key for one slot's **value sentence** — `approval.<door>.<inputPath>`.\n *\n * `inputPath` and not `role`: a door repeats `consequence`, so two slots in one door would collide\n * on role and the later sentence would silently take the earlier one's key. `inputPath` is the one\n * thing already unique within a door, and it is already written in the declaration.\n */\nexport function decisionValueKey(activityName: string, inputPath: string): string {\n return `approval.${doorSlug(activityName)}.${inputPath}`\n}\n\n/**\n * The i18n key for an activity's own sentences — `activity.<door>.label`, `activity.<door>.guide.<n>`.\n *\n * Computed from `name` for the reason decisionValueKey is: nobody picks the key, so none is picked\n * wrong, and an app's test can walk its declarations and count every key that must exist.\n *\n * The server sends the result (Activity.labelTKey, InstallableActivity.labelTKey and guideTKeys);\n * screens draw `t(labelTKey, { defaultValue: label })` and never rebuild the slug. A client file that\n * imported this one would move the whole client build under dist-client/client/.\n *\n * `description` has no key. It is the developers' English line and is not shown to the person\n * doing the work.\n *\n * Guide lines are keyed by position. Inserting a line shifts every later translation by one, and a\n * count of keys does not notice — change guide and its translations in the same edit.\n */\nexport function activityTKey(activityName: string, part: 'label' | 'readiness-notes'): string\nexport function activityTKey(activityName: string, part: 'guide' | 'readiness-gap', index: number): string\nexport function activityTKey(\n activityName: string,\n part: 'label' | 'guide' | 'readiness-notes' | 'readiness-gap',\n index?: number\n): string {\n const door = `activity.${doorSlug(activityName)}`\n\n switch (part) {\n case 'guide':\n return `${door}.guide.${index}`\n case 'readiness-gap':\n return `${door}.readiness-gap.${index}`\n case 'readiness-notes':\n return `${door}.readiness-notes`\n default:\n return `${door}.label`\n }\n}\n\n/**\n * One key per guide line, **always as many as there are lines.** The screen pairs the two arrays\n * by position; a shorter key list would send the last lines out untranslated with nothing to show it.\n */\nexport function activityGuideTKeys(activityName: string, guide: string[] | undefined | null): string[] {\n return (guide || []).map((_, index) => activityTKey(activityName, 'guide', index))\n}\n\n/**\n * The same for what a module declares about its own readiness.\n *\n * The catalog screen draws `readiness.gaps` and `readiness.notes` as sentences, and those come out\n * of a catalog that is built at boot, before anyone's locale is known. So the module keeps writing\n * sentences and the screen reads a translation of them, exactly as it does for label and guide\n * (ADR-0054: the sentence is chosen where `context.t` is).\n */\nexport function activityReadinessGapTKeys(activityName: string, gaps: string[] | undefined | null): string[] {\n return (gaps || []).map((_, index) => activityTKey(activityName, 'readiness-gap', index))\n}\n\nexport function assertDecisionKeysAreUsable(\n decisionKeys: DecisionKeyDeclaration[] | undefined,\n activityName?: string\n): void {\n if (!decisionKeys?.length) {\n return\n }\n\n const named = activityName ? `activity \"${activityName}\"` : 'this activity'\n\n const unknown = decisionKeys.filter(key => !(DECISION_ROLES as readonly string[]).includes(String(key?.role ?? '')))\n\n if (unknown.length) {\n throw new Error(\n `${named} declares decision keys with a role the approval screen does not know: ` +\n `${unknown.map(key => `\"${key?.role ?? ''}\"`).join(', ')}. ` +\n `Known roles are ${DECISION_ROLES.join(', ')}. A role outside that set draws nothing.`\n )\n }\n\n const pathless = decisionKeys.filter(key => !String(key?.inputPath ?? '').trim())\n\n if (pathless.length) {\n throw new Error(\n `${named} declares ${pathless.length} decision key(s) with no inputPath. ` +\n `A decision key names the value it draws; without a path there is nothing to read.`\n )\n }\n\n /*\n * An index or a wildcard resolves to nothing at draw time and the slot would report \"the path led\n * nowhere\" — which reads as a payload that disagrees with the declaration, when the truth is that\n * the declaration used a path language that does not exist. Refusing here says which it was.\n */\n const notPaths = decisionKeys.filter(key => /[[\\]*]/.test(String(key?.inputPath ?? '')))\n\n if (notPaths.length) {\n throw new Error(\n `${named} declares decision keys whose inputPath is not a plain dotted path: ` +\n `${notPaths.map(key => `\"${key?.inputPath ?? ''}\"`).join(', ')}. ` +\n `Dots only — no array index and no wildcard. Point at the list itself; drawing its items is ` +\n `the slot's work.`\n )\n }\n\n const repeated = DECISION_ROLES.filter(\n role =>\n !(REPEATABLE_ROLES as readonly string[]).includes(role) &&\n decisionKeys.filter(key => key?.role === role).length > 1\n )\n\n if (repeated.length) {\n throw new Error(\n `${named} declares more than one \"${repeated.join('\", \"')}\" decision key. ` +\n `Only ${REPEATABLE_ROLES.map(role => `\"${role}\"`).join(' and ')} may repeat — an approval can ` +\n `change several things at once, and grounds come in several. The screen would draw one of the ` +\n `others and could not say which.`\n )\n }\n\n /*\n * A sentence may only be built from values that were snapshotted, so every params value is an\n * inputPath and reads by the same rules. Wanting a value with no field behind it means the field\n * is missing, not that params needs a second source — the two hold each other up: without the\n * value the sentence cannot be rebuilt, and a value a sentence uses is guaranteed to stay.\n *\n * Only the right side has a rule. The names on the left belong to the sentence and are the\n * locale file's business.\n */\n const badParams: string[] = []\n\n for (const key of decisionKeys) {\n for (const [name, path] of Object.entries(key?.params || {})) {\n const said = String(path ?? '')\n\n if (!said.trim()) {\n badParams.push(`\"${name}\" (empty)`)\n } else if (/[[\\]*]/.test(said)) {\n badParams.push(`\"${name}\": \"${said}\"`)\n }\n }\n }\n\n if (badParams.length) {\n throw new Error(\n `${named} declares sentence params that are not plain dotted paths into the input: ` +\n `${badParams.join(', ')}. Every params value is an inputPath — a sentence is built only from ` +\n `values the request froze, so a value with nowhere to read it from is a missing field.`\n )\n }\n\n if (!activityName) {\n return\n }\n\n /*\n * The key is computed, so a declared one that disagrees is a typo that would draw `approval.…` on\n * the card. Checking it here rather than trusting the writer is the whole reason it is computed.\n */\n const wrongKey = decisionKeys.filter(\n key => key?.valueTKey && key.valueTKey !== decisionValueKey(activityName, String(key?.inputPath ?? ''))\n )\n\n if (wrongKey.length) {\n throw new Error(\n `${named} declares value keys that are not the computed ones: ` +\n `${wrongKey\n .map(\n key => `\"${key?.valueTKey}\" (expected \"${decisionValueKey(activityName, String(key?.inputPath ?? ''))}\")`\n )\n .join(', ')}. ` +\n `The key is approval.<door>.<inputPath> and nobody picks it — a key nobody can pick is a key ` +\n `nobody can pick wrong.`\n )\n }\n}\n"]}
@@ -8,4 +8,15 @@ export declare class InstallableActivityQuery {
8
8
  labelTKey(one: InstallableActivity): string;
9
9
  /** One key per `guide` line, in the same order and always the same length. */
10
10
  guideTKeys(one: InstallableActivity): string[];
11
+ /**
12
+ * The keys `readiness.notes` and `readiness.gaps` are translated from.
13
+ *
14
+ * The catalog is built at boot, before any request names a locale, so the module keeps declaring
15
+ * sentences and the screen reads a translation of them - the same arrangement as label and guide
16
+ * (ADR-0054). A module that declares nothing gets no keys to look up, and the screen shows
17
+ * nothing, which is what it did before.
18
+ */
19
+ readinessNotesTKey(one: InstallableActivity): string;
20
+ /** One key per `readiness.gaps` line, in the same order and always the same length. */
21
+ readinessGapTKeys(one: InstallableActivity): string[];
11
22
  }
@@ -58,6 +58,21 @@ let InstallableActivityQuery = class InstallableActivityQuery {
58
58
  guideTKeys(one) {
59
59
  return (0, decision_key_declaration_js_1.activityGuideTKeys)(one.name, one.guide);
60
60
  }
61
+ /**
62
+ * The keys `readiness.notes` and `readiness.gaps` are translated from.
63
+ *
64
+ * The catalog is built at boot, before any request names a locale, so the module keeps declaring
65
+ * sentences and the screen reads a translation of them - the same arrangement as label and guide
66
+ * (ADR-0054). A module that declares nothing gets no keys to look up, and the screen shows
67
+ * nothing, which is what it did before.
68
+ */
69
+ readinessNotesTKey(one) {
70
+ return (0, decision_key_declaration_js_1.activityTKey)(one.name, 'readiness-notes');
71
+ }
72
+ /** One key per `readiness.gaps` line, in the same order and always the same length. */
73
+ readinessGapTKeys(one) {
74
+ return (0, decision_key_declaration_js_1.activityReadinessGapTKeys)(one.name, one.readiness?.gaps);
75
+ }
61
76
  };
62
77
  exports.InstallableActivityQuery = InstallableActivityQuery;
63
78
  tslib_1.__decorate([
@@ -90,6 +105,22 @@ tslib_1.__decorate([
90
105
  tslib_1.__metadata("design:paramtypes", [installable_activity_js_1.InstallableActivity]),
91
106
  tslib_1.__metadata("design:returntype", Array)
92
107
  ], InstallableActivityQuery.prototype, "guideTKeys", null);
108
+ tslib_1.__decorate([
109
+ (0, type_graphql_1.FieldResolver)(type => String, { description: 'i18n key readiness.notes is translated from, computed from name' }),
110
+ tslib_1.__param(0, (0, type_graphql_1.Root)()),
111
+ tslib_1.__metadata("design:type", Function),
112
+ tslib_1.__metadata("design:paramtypes", [installable_activity_js_1.InstallableActivity]),
113
+ tslib_1.__metadata("design:returntype", String)
114
+ ], InstallableActivityQuery.prototype, "readinessNotesTKey", null);
115
+ tslib_1.__decorate([
116
+ (0, type_graphql_1.FieldResolver)(type => [String], {
117
+ description: 'One i18n key per readiness.gaps line, in order, computed from name'
118
+ }),
119
+ tslib_1.__param(0, (0, type_graphql_1.Root)()),
120
+ tslib_1.__metadata("design:type", Function),
121
+ tslib_1.__metadata("design:paramtypes", [installable_activity_js_1.InstallableActivity]),
122
+ tslib_1.__metadata("design:returntype", Array)
123
+ ], InstallableActivityQuery.prototype, "readinessGapTKeys", null);
93
124
  exports.InstallableActivityQuery = InstallableActivityQuery = tslib_1.__decorate([
94
125
  (0, type_graphql_1.Resolver)(installable_activity_js_1.InstallableActivity)
95
126
  ], InstallableActivityQuery);
@@ -1 +1 @@
1
- {"version":3,"file":"installable-activity-query.js","sourceRoot":"","sources":["../../../server/service/installable-activity/installable-activity-query.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Args, Ctx, Query, Resolver, FieldResolver, Root } from 'type-graphql'\nimport { Domain, getRepository, ListParam } from '@things-factory/shell'\n\nimport { ActivityInstallations } from '../../controllers/activity-installation/activity-installation-controller.js'\nimport { Activity } from '../activity/activity.js'\nimport { InstallableActivity } from './installable-activity.js'\nimport { isApprovalStep } from '../../controllers/activity-approval/approval-line.js'\nimport { InstallableActivityList } from './installable-activity-type.js'\nimport { activityGuideTKeys, activityTKey } from '../activity/decision-key-declaration.js'\n\n@Resolver(InstallableActivity)\nexport class InstallableActivityQuery {\n @Query(returns => InstallableActivity!, { nullable: true, description: 'To fetch a InstallableActivity' })\n async installableActivity(@Arg('name') name: string, @Ctx() context: ResolverContext): Promise<InstallableActivity> {\n return await ActivityInstallations.get(name)\n }\n\n @Query(returns => InstallableActivityList, { description: 'To fetch multiple InstallableActivitys' })\n async installableActivities(\n @Args(type => ListParam) params: ListParam,\n @Ctx() context: ResolverContext\n ): Promise<InstallableActivityList> {\n const { domain } = context.state\n\n const items = ActivityInstallations.list()\n\n for (var item of items) {\n item.activity = await getRepository(Activity).findOneBy({\n domain: { id: domain.id },\n name: item.name\n })\n\n /*\n * 「이 도메인이 이 단계를 쓰나」를 값으로 낸다 — 판단은 여기서 다시 짜지 않는다.\n *\n * `isApprovalStep` 이 참조를 걸러 내는 그 자리다. 화면이 길이를 세면 참조만 있는 결재선을\n * 「결재자 있음」으로 그리고, 실제로는 그냥 지나간다.\n */\n item.approvalSteps = (item.activity?.approvalLine || []).filter(isApprovalStep).length\n }\n\n /*\n * Identified-but-unbuilt work comes out of this query too, because the catalog is the plan of\n * record — \"what is left\" has to be answerable on the screen.\n *\n * Shaped to look like an installable activity so the screen has one list to draw, but it\n * carries no `activity` (nothing is installed) and no callback. Its stage says planned or\n * not-worklist, and the screen keeps those apart from the built ones: a decision must not read\n * as a promise.\n */\n const intended: InstallableActivity[] = ActivityInstallations.listIntended().map(one => ({\n name: one.key,\n label: one.label,\n description: undefined,\n guide: one.guide,\n category: one.category,\n readiness: one.readiness\n }))\n\n const merged = [...items, ...intended]\n\n return { items: merged, total: merged.length }\n }\n\n /** The key `label` is translated from. Computed from `name`, never stored — see activityTKey. */\n @FieldResolver(type => String, { description: 'i18n key the label is translated from, computed from name' })\n labelTKey(@Root() one: InstallableActivity): string {\n return activityTKey(one.name, 'label')\n }\n\n /** One key per `guide` line, in the same order and always the same length. */\n @FieldResolver(type => [String], { description: 'One i18n key per guide line, in order, computed from name' })\n guideTKeys(@Root() one: InstallableActivity): string[] {\n return activityGuideTKeys(one.name, one.guide)\n }\n}\n"]}
1
+ {"version":3,"file":"installable-activity-query.js","sourceRoot":"","sources":["../../../server/service/installable-activity/installable-activity-query.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Args, Ctx, Query, Resolver, FieldResolver, Root } from 'type-graphql'\nimport { Domain, getRepository, ListParam } from '@things-factory/shell'\n\nimport { ActivityInstallations } from '../../controllers/activity-installation/activity-installation-controller.js'\nimport { Activity } from '../activity/activity.js'\nimport { InstallableActivity } from './installable-activity.js'\nimport { isApprovalStep } from '../../controllers/activity-approval/approval-line.js'\nimport { InstallableActivityList } from './installable-activity-type.js'\nimport { activityGuideTKeys, activityReadinessGapTKeys, activityTKey } from '../activity/decision-key-declaration.js'\n\n@Resolver(InstallableActivity)\nexport class InstallableActivityQuery {\n @Query(returns => InstallableActivity!, { nullable: true, description: 'To fetch a InstallableActivity' })\n async installableActivity(@Arg('name') name: string, @Ctx() context: ResolverContext): Promise<InstallableActivity> {\n return await ActivityInstallations.get(name)\n }\n\n @Query(returns => InstallableActivityList, { description: 'To fetch multiple InstallableActivitys' })\n async installableActivities(\n @Args(type => ListParam) params: ListParam,\n @Ctx() context: ResolverContext\n ): Promise<InstallableActivityList> {\n const { domain } = context.state\n\n const items = ActivityInstallations.list()\n\n for (var item of items) {\n item.activity = await getRepository(Activity).findOneBy({\n domain: { id: domain.id },\n name: item.name\n })\n\n /*\n * 「이 도메인이 이 단계를 쓰나」를 값으로 낸다 — 판단은 여기서 다시 짜지 않는다.\n *\n * `isApprovalStep` 이 참조를 걸러 내는 그 자리다. 화면이 길이를 세면 참조만 있는 결재선을\n * 「결재자 있음」으로 그리고, 실제로는 그냥 지나간다.\n */\n item.approvalSteps = (item.activity?.approvalLine || []).filter(isApprovalStep).length\n }\n\n /*\n * Identified-but-unbuilt work comes out of this query too, because the catalog is the plan of\n * record — \"what is left\" has to be answerable on the screen.\n *\n * Shaped to look like an installable activity so the screen has one list to draw, but it\n * carries no `activity` (nothing is installed) and no callback. Its stage says planned or\n * not-worklist, and the screen keeps those apart from the built ones: a decision must not read\n * as a promise.\n */\n const intended: InstallableActivity[] = ActivityInstallations.listIntended().map(one => ({\n name: one.key,\n label: one.label,\n description: undefined,\n guide: one.guide,\n category: one.category,\n readiness: one.readiness\n }))\n\n const merged = [...items, ...intended]\n\n return { items: merged, total: merged.length }\n }\n\n /** The key `label` is translated from. Computed from `name`, never stored — see activityTKey. */\n @FieldResolver(type => String, { description: 'i18n key the label is translated from, computed from name' })\n labelTKey(@Root() one: InstallableActivity): string {\n return activityTKey(one.name, 'label')\n }\n\n /** One key per `guide` line, in the same order and always the same length. */\n @FieldResolver(type => [String], { description: 'One i18n key per guide line, in order, computed from name' })\n guideTKeys(@Root() one: InstallableActivity): string[] {\n return activityGuideTKeys(one.name, one.guide)\n }\n\n /**\n * The keys `readiness.notes` and `readiness.gaps` are translated from.\n *\n * The catalog is built at boot, before any request names a locale, so the module keeps declaring\n * sentences and the screen reads a translation of them - the same arrangement as label and guide\n * (ADR-0054). A module that declares nothing gets no keys to look up, and the screen shows\n * nothing, which is what it did before.\n */\n @FieldResolver(type => String, { description: 'i18n key readiness.notes is translated from, computed from name' })\n readinessNotesTKey(@Root() one: InstallableActivity): string {\n return activityTKey(one.name, 'readiness-notes')\n }\n\n /** One key per `readiness.gaps` line, in the same order and always the same length. */\n @FieldResolver(type => [String], {\n description: 'One i18n key per readiness.gaps line, in order, computed from name'\n })\n readinessGapTKeys(@Root() one: InstallableActivity): string[] {\n return activityReadinessGapTKeys(one.name, one.readiness?.gaps)\n }\n}\n"]}