@conduction/nextcloud-vue 2.70.0 → 2.72.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +151 -13
  2. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  3. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +3 -1
  4. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  5. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  6. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +38 -42
  7. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  8. package/dist/esm/components/CnFlowDetail/CnRunDetailSidebar.vue2.js +2 -0
  9. package/dist/esm/components/CnFlowDetail/CnRunDetailSidebar.vue2.js.map +1 -1
  10. package/dist/esm/components/CnFlowDetail/CnRunDetailSidebar.vue3.js +7 -0
  11. package/dist/esm/components/CnFlowDetail/CnRunDetailSidebar.vue3.js.map +1 -1
  12. package/dist/esm/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue.js +10 -0
  13. package/dist/esm/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue.js.map +1 -0
  14. package/dist/esm/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue2.js +150 -0
  15. package/dist/esm/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue2.js.map +1 -0
  16. package/dist/esm/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue3.js +54 -0
  17. package/dist/esm/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue3.js.map +1 -0
  18. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +29 -2
  19. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
  20. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +71 -34
  21. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
  22. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +22 -0
  23. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  24. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +1 -1
  25. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  26. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js +1 -1
  27. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js.map +1 -1
  28. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue3.js.map +1 -1
  29. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
  30. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +46 -32
  31. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
  32. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js +30 -9
  33. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js.map +1 -1
  34. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js.map +1 -1
  35. package/dist/esm/composables/useWalkthrough.js +11 -3
  36. package/dist/esm/composables/useWalkthrough.js.map +1 -1
  37. package/dist/esm/dialogs/CnFlowNodeEditModal.vue2.js +7 -10
  38. package/dist/esm/dialogs/CnFlowNodeEditModal.vue2.js.map +1 -1
  39. package/dist/esm/dialogs/CnFlowNodeEditModal.vue3.js +23 -13
  40. package/dist/esm/dialogs/CnFlowNodeEditModal.vue3.js.map +1 -1
  41. package/dist/esm/dialogs/CnRunNodeDialog.vue2.js +8 -6
  42. package/dist/esm/dialogs/CnRunNodeDialog.vue2.js.map +1 -1
  43. package/dist/esm/dialogs/CnRunNodeDialog.vue3.js.map +1 -1
  44. package/dist/esm/index.js +1 -0
  45. package/dist/esm/index.js.map +1 -1
  46. package/dist/esm/l10n/en.json.js +15 -1
  47. package/dist/esm/l10n/en.json.js.map +1 -1
  48. package/dist/esm/l10n/nl.json.js +15 -1
  49. package/dist/esm/l10n/nl.json.js.map +1 -1
  50. package/dist/esm/nextcloud-vue.css +117 -0
  51. package/dist/esm/utils/columnFilters.js +54 -4
  52. package/dist/esm/utils/columnFilters.js.map +1 -1
  53. package/dist/esm/utils/selectOptions.js +57 -0
  54. package/dist/esm/utils/selectOptions.js.map +1 -0
  55. package/dist/esm/utils/userAutocomplete.js +69 -3
  56. package/dist/esm/utils/userAutocomplete.js.map +1 -1
  57. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  58. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  59. package/dist/nextcloud-vue.cjs.js +6560 -5880
  60. package/dist/nextcloud-vue.cjs.js.map +2 -2
  61. package/dist/nextcloud-vue.css +117 -0
  62. package/l10n/en.json +15 -1
  63. package/l10n/nl.json +15 -1
  64. package/package.json +1 -1
  65. package/src/components/CnAppRoot/CnAppRoot.vue +153 -13
  66. package/src/components/CnDashboardPage/CnDashboardPage.vue +20 -18
  67. package/src/components/CnFlowDetail/CnRunDetailSidebar.vue +6 -0
  68. package/src/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue +250 -0
  69. package/src/components/CnFlowStepOutcomes/index.js +8 -0
  70. package/src/components/CnFormDialog/CnFormDialog.vue +62 -2
  71. package/src/components/CnIndexPage/CnIndexPage.vue +23 -1
  72. package/src/components/CnObjectSidebar/CnNotesTab.vue +1 -1
  73. package/src/components/CnSetupWizard/CnSetupWizard.vue +7 -0
  74. package/src/components/CnWalkthrough/CnWalkthrough.vue +30 -9
  75. package/src/components/index.js +1 -0
  76. package/src/composables/useWalkthrough.js +11 -3
  77. package/src/css/dashboard.css +44 -0
  78. package/src/dialogs/CnFlowNodeEditModal.vue +41 -20
  79. package/src/dialogs/CnRunNodeDialog.vue +8 -6
  80. package/src/index.js +1 -0
  81. package/src/schemas/app-manifest-v2.schema.json +2 -1
  82. package/src/utils/columnFilters.js +54 -4
  83. package/src/utils/selectOptions.js +54 -0
  84. package/src/utils/userAutocomplete.js +68 -2
  85. package/src/utils/validateManifestV2.compiled.js +2 -2
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: package/dist/nextcloud-vue.cjs.js.map
3
- size: 35617052 bytes
4
- sha256: 8d17da3a5f3f43dba63932341520f63af085b4d61c1b614631488abb6ecd41d8
3
+ size: 35684577 bytes
4
+ sha256: 40220f92c6defbd478644408d9d23a88047c47798fb52eefee28e6d70e0fbc9a
@@ -2841,6 +2841,50 @@ a.cn-kpi-card--stacked:focus-visible {
2841
2841
  .cn-requires-app.cn-requires-app .empty-content__action {
2842
2842
  margin-top: var(--default-grid-baseline, 4px);
2843
2843
  }
2844
+
2845
+ /* NcEmptyContent forces its icon's svg to 64px with !important while the
2846
+ * rule above lets the icon box shrink, so the svg spilled out of its box and
2847
+ * over the name. Hold the svg to the size the caller asked for. */
2848
+ .cn-requires-app.cn-requires-app .empty-content__icon svg {
2849
+ width: 32px !important;
2850
+ height: 32px !important;
2851
+ max-width: 32px !important;
2852
+ max-height: 32px !important;
2853
+ }
2854
+
2855
+ /* The dashboard's compact "Install <app>" placeholder: two lines of text and
2856
+ * the button, nothing else, so it fits a two-row tile even under a title row.
2857
+ * `safe center` keeps the top in view when a tile is smaller still, and the
2858
+ * body scrolls rather than clipping the button. */
2859
+ .cn-requires-app--compact {
2860
+ display: flex;
2861
+ flex-direction: column;
2862
+ align-items: center;
2863
+ justify-content: safe center;
2864
+ gap: calc(2 * var(--default-grid-baseline, 4px));
2865
+ box-sizing: border-box;
2866
+ height: 100%;
2867
+ min-height: 0;
2868
+ overflow: auto;
2869
+ padding: calc(2 * var(--default-grid-baseline, 4px));
2870
+ text-align: center;
2871
+ }
2872
+
2873
+ .cn-requires-app--compact .cn-requires-app__description {
2874
+ display: -webkit-box;
2875
+ margin: 0;
2876
+ overflow: hidden;
2877
+ color: var(--color-text-maxcontrast);
2878
+ font-size: 0.9em;
2879
+ line-height: 1.4;
2880
+ -webkit-box-orient: vertical;
2881
+ -webkit-line-clamp: 2;
2882
+ line-clamp: 2;
2883
+ }
2884
+
2885
+ .cn-requires-app--compact .button-vue {
2886
+ flex-shrink: 0;
2887
+ }
2844
2888
  /* ========================================
2845
2889
  @conduction/nextcloud-vue — CnDetailPage Styles
2846
2890
  ======================================== */
@@ -15645,6 +15689,15 @@ a.cn-xwiki-card__title[data-v-54e85fe3]:hover {
15645
15689
  }
15646
15690
 
15647
15691
 
15692
+ /* A select option breaks between words only, never inside one. */
15693
+ .cn-form-dialog__option[data-v-c3eeb0a2] {
15694
+ display: block;
15695
+ min-width: 0;
15696
+ white-space: normal;
15697
+ overflow-wrap: normal;
15698
+ word-break: normal;
15699
+ hyphens: manual;
15700
+ }
15648
15701
  .cn-form-dialog__draft-actions[data-v-c3eeb0a2] {
15649
15702
  display: flex;
15650
15703
  gap: 8px;
@@ -26529,6 +26582,18 @@ a.cn-flow-runs-widget__row-content:hover .cn-flow-runs-widget__name[data-v-75628
26529
26582
  font-size: 0.85em;
26530
26583
  color: var(--color-text-maxcontrast);
26531
26584
  }
26585
+
26586
+ /* Full width, like the text fields around it: at its natural width the
26587
+ picker cut every option label in half. */
26588
+ .cn-flow-node-edit__select[data-v-e107484a] {
26589
+ width: 100%;
26590
+ }
26591
+ .cn-flow-node-edit__field-help[data-v-e107484a] {
26592
+ margin: 4px 0 0;
26593
+ padding-inline-start: 8px;
26594
+ font-size: 0.85em;
26595
+ color: var(--color-text-maxcontrast);
26596
+ }
26532
26597
  .cn-flow-node-edit__advanced summary[data-v-e107484a] {
26533
26598
  cursor: pointer;
26534
26599
  color: var(--color-text-maxcontrast);
@@ -27983,6 +28048,58 @@ to {
27983
28048
  }
27984
28049
 
27985
28050
 
28051
+ .cn-flow-step-outcomes[data-v-5ac5ad4f] {
28052
+ margin: 4px 0 0;
28053
+ font-size: 0.9em;
28054
+ }
28055
+ .cn-flow-step-outcomes__list[data-v-5ac5ad4f] {
28056
+ margin: 0;
28057
+ padding: 0;
28058
+ }
28059
+
28060
+ /* Stacked, name over value: the run sidebar is narrow, and a side-by-side
28061
+ row wrapped mid-list. The host's dt/dd margins and alignment are reset. */
28062
+ .cn-flow-step-outcomes__row[data-v-5ac5ad4f] {
28063
+ display: block;
28064
+ margin-block: 4px;
28065
+ padding-block: 2px;
28066
+ padding-inline-start: 8px;
28067
+ border-inline-start: 3px solid var(--color-border);
28068
+ text-align: start;
28069
+ }
28070
+ .cn-flow-step-outcomes__row--success[data-v-5ac5ad4f] {
28071
+ border-inline-start-color: var(--color-success);
28072
+ }
28073
+ .cn-flow-step-outcomes__row--warning[data-v-5ac5ad4f] {
28074
+ border-inline-start-color: var(--color-warning);
28075
+ }
28076
+ .cn-flow-step-outcomes__row--error[data-v-5ac5ad4f] {
28077
+ border-inline-start-color: var(--color-error);
28078
+ }
28079
+ .cn-flow-step-outcomes__name[data-v-5ac5ad4f] {
28080
+ display: block;
28081
+ white-space: normal;
28082
+ overflow-wrap: anywhere;
28083
+ margin: 0;
28084
+ padding: 0;
28085
+ font-weight: bold;
28086
+ text-align: start;
28087
+ }
28088
+ .cn-flow-step-outcomes__value[data-v-5ac5ad4f] {
28089
+ display: block;
28090
+ white-space: normal;
28091
+ margin: 0;
28092
+ padding: 0;
28093
+ text-align: start;
28094
+ color: var(--color-main-text);
28095
+ overflow-wrap: anywhere;
28096
+ }
28097
+ .cn-flow-step-outcomes__truncated[data-v-5ac5ad4f] {
28098
+ margin: 4px 0 0;
28099
+ color: var(--color-text-maxcontrast);
28100
+ }
28101
+
28102
+
27986
28103
  .cn-run-sidebar[data-v-48e781f0] {
27987
28104
  display: flex;
27988
28105
  flex-direction: column;
package/l10n/en.json CHANGED
@@ -1106,7 +1106,21 @@
1106
1106
  "Loading…": "Loading…",
1107
1107
  "A register and schema are required": "A register and schema are required",
1108
1108
  "Change the search or try again.": "Change the search or try again.",
1109
- "Try again later.": "Try again later."
1109
+ "Try again later.": "Try again later.",
1110
+ "Who this step reached": "Who this step reached",
1111
+ "Each list shows its first entries only.": "Each list shows its first entries only.",
1112
+ "Delivered": "Delivered",
1113
+ "Opted out": "Opted out",
1114
+ "Not sent, the opt-out check did not answer": "Not sent, the opt-out check did not answer",
1115
+ "Skipped by their notification settings": "Skipped by their notification settings",
1116
+ "Skipped, sending is switched off": "Skipped, sending is switched off",
1117
+ "Held back by the send limit": "Held back by the send limit",
1118
+ "Refused by the step’s address rule": "Refused by the step’s address rule",
1119
+ "Not a known user or group": "Not a known user or group",
1120
+ "Failed": "Failed",
1121
+ "external recipients are off": "external recipients are off",
1122
+ "not on the item": "not on the item",
1123
+ "not a valid address": "not a valid address"
1110
1124
  },
1111
1125
  "plurals": {
1112
1126
  "%n year": ["%n year", "%n years"],
package/l10n/nl.json CHANGED
@@ -1150,7 +1150,21 @@
1150
1150
  "Loading…": "Laden…",
1151
1151
  "A register and schema are required": "Een register en schema zijn verplicht",
1152
1152
  "Change the search or try again.": "Pas de zoekopdracht aan of probeer het opnieuw.",
1153
- "Try again later.": "Probeer het later opnieuw."
1153
+ "Try again later.": "Probeer het later opnieuw.",
1154
+ "Who this step reached": "Wie deze stap bereikte",
1155
+ "Each list shows its first entries only.": "Elke lijst toont alleen de eerste vermeldingen.",
1156
+ "Delivered": "Bezorgd",
1157
+ "Opted out": "Afgemeld",
1158
+ "Not sent, the opt-out check did not answer": "Niet verstuurd, de afmeldcontrole gaf geen antwoord",
1159
+ "Skipped by their notification settings": "Overgeslagen door hun meldingsinstellingen",
1160
+ "Skipped, sending is switched off": "Overgeslagen, verzenden staat uit",
1161
+ "Held back by the send limit": "Tegengehouden door de verzendlimiet",
1162
+ "Refused by the step’s address rule": "Geweigerd door de adresregel van de stap",
1163
+ "Not a known user or group": "Geen bekende gebruiker of groep",
1164
+ "Failed": "Mislukt",
1165
+ "external recipients are off": "externe ontvangers staan uit",
1166
+ "not on the item": "staat niet op het item",
1167
+ "not a valid address": "geen geldig adres"
1154
1168
  },
1155
1169
  "plurals": {
1156
1170
  "%n year": ["%n jaar", "%n jaar"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@conduction/nextcloud-vue",
3
- "version": "2.70.0",
3
+ "version": "2.72.0",
4
4
  "description": "Shared Vue component library for Conduction Nextcloud apps — complements @nextcloud/vue with higher-level components, OpenRegister integration, and NL Design System support",
5
5
  "homepage": "https://github.com/ConductionNL/nextcloud-vue#readme",
6
6
  "bugs": {
@@ -513,8 +513,10 @@
513
513
  :manifest="manifest"
514
514
  :seenVersion="walkthroughSeenVersion"
515
515
  :resume="walkthroughResume"
516
+ :autoStart="!walkthroughPaused"
516
517
  :translate="translate"
517
518
  @complete="onWalkthroughComplete"
519
+ @pause="onWalkthroughPause"
518
520
  @progress="onWalkthroughProgress" />
519
521
  </slot>
520
522
  <!--
@@ -934,8 +936,11 @@ export default {
934
936
  * Restart entry for the product walkthrough (ADR-043). Descendants
935
937
  * (a menu/settings "Replay walkthrough" entry, or a manifest menu
936
938
  * `action: "replay-walkthrough"`) call this to re-run a tour. With no
937
- * `tourId` the first declared tour is used.
939
+ * `tourId` the first declared tour is used. An unfinished tour
940
+ * continues where the user was (a paused tour, else the saved
941
+ * step); "Start over" in the user settings goes back to step 1.
938
942
  *
943
+ * @spec openspec/changes/audit-round-lib-fixes/specs/cn-walkthrough/spec.md
939
944
  * @param {string} [tourId] The tour to restart.
940
945
  * @return {void}
941
946
  */
@@ -945,9 +950,18 @@ export default {
945
950
  }
946
951
  const wt = useWalkthrough(this.appId, this.manifest)
947
952
  const id = tourId || (this.manifest.walkthrough.tours[0] && this.manifest.walkthrough.tours[0].id)
948
- if (id) {
949
- wt.restart(id)
953
+ if (!id) {
954
+ return
955
+ }
956
+ const progress = this.walkthroughProgressValue
957
+ this.unpauseWalkthroughProgress()
958
+ if (wt.paused.value && wt.activeTour.value && wt.activeTour.value.id === id && wt.resumePaused()) {
959
+ return
960
+ }
961
+ if (progress && progress.tourId === id && wt.resumeAt(id, progress.stepId)) {
962
+ return
950
963
  }
964
+ wt.restart(id)
951
965
  },
952
966
 
953
967
  /**
@@ -1528,7 +1542,7 @@ export default {
1528
1542
  },
1529
1543
  },
1530
1544
 
1531
- emits: ['setup-complete', 'walkthrough-complete'],
1545
+ emits: ['setup-complete', 'setup-wizard-dismissed', 'walkthrough-complete'],
1532
1546
 
1533
1547
  /**
1534
1548
  * Component-instance state for the capabilities guard.
@@ -2599,9 +2613,21 @@ export default {
2599
2613
  } catch {
2600
2614
  // No URL token; fall through to the remembered progress.
2601
2615
  }
2602
- // An unfinished tour continues at the step the user reached.
2616
+ // An unfinished tour continues at the step the user reached,
2617
+ // unless the user paused it: then it waits for "Continue".
2603
2618
  const progress = this.walkthroughProgressValue
2604
- return progress ? { tourId: progress.tourId, stepId: progress.stepId } : null
2619
+ return progress && !progress.paused ? { tourId: progress.tourId, stepId: progress.stepId } : null
2620
+ },
2621
+
2622
+ /**
2623
+ * Whether the user paused the tour (X, ESC or the dim). A paused tour
2624
+ * stays hidden across page loads until the user picks "Continue".
2625
+ *
2626
+ * @spec openspec/changes/audit-round-lib-fixes/specs/cn-walkthrough/spec.md
2627
+ * @return {boolean} True while the saved progress is paused.
2628
+ */
2629
+ walkthroughPaused() {
2630
+ return !!(this.walkthroughProgressValue && this.walkthroughProgressValue.paused)
2605
2631
  },
2606
2632
 
2607
2633
  phase() {
@@ -3281,7 +3307,7 @@ export default {
3281
3307
  // the server keeps reporting it unmet — so without this the wizard
3282
3308
  // auto-opens again on every visit. Cancel must not be the only way to
3283
3309
  // get peace.
3284
- this.persistSetupWizardDismissal()
3310
+ this.persistSetupWizardDismissal(true)
3285
3311
  if (this.setupState && typeof this.setupState.refresh === 'function') {
3286
3312
  this.setupState.refresh()
3287
3313
  }
@@ -3302,7 +3328,7 @@ export default {
3302
3328
  * @return {void}
3303
3329
  */
3304
3330
  dismissSetupWizard() {
3305
- this.persistSetupWizardDismissal()
3331
+ this.persistSetupWizardDismissal(false)
3306
3332
  this.setupWizardOpen = false
3307
3333
  },
3308
3334
 
@@ -3325,7 +3351,7 @@ export default {
3325
3351
  * @return {boolean}
3326
3352
  */
3327
3353
  isSetupWizardDismissed() {
3328
- if (this.setupWizardDismissed) {
3354
+ if (this.setupWizardDismissed || this.isSetupWizardDismissedOnServer()) {
3329
3355
  return true
3330
3356
  }
3331
3357
  try {
@@ -3336,18 +3362,87 @@ export default {
3336
3362
  },
3337
3363
 
3338
3364
  /**
3339
- * Record the non-gating setup wizard as dismissed for this manifest
3340
- * `setup.version` so it doesn't auto-open again.
3365
+ * Whether the app's setup status says the wizard was closed, so it
3366
+ * stays closed in every browser and on every device. The status
3367
+ * (`GET /api/setup/status`) may carry `dismissed: true`, or the
3368
+ * `setup.version` it was closed at (`dismissed: 2`), which re-opens the
3369
+ * wizard after a version bump. An app whose status carries neither
3370
+ * keeps today's per-browser behaviour. An app may also answer the
3371
+ * outstanding steps itself when it records the close (OpenRegister's
3372
+ * `dismiss-setup`), which needs nothing here.
3373
+ *
3374
+ * @spec openspec/changes/setup-wizard-close-on-server/specs/cn-setup-wizard/spec.md
3375
+ * @return {boolean} True when the server recorded the close for this version.
3376
+ */
3377
+ isSetupWizardDismissedOnServer() {
3378
+ const s = this.setupState
3379
+ const status = s && s.status && s.status.value
3380
+ const dismissed = status && typeof status === 'object' ? status.dismissed : undefined
3381
+ if (dismissed === true) {
3382
+ return true
3383
+ }
3384
+ if (typeof dismissed === 'number' && Number.isFinite(dismissed)) {
3385
+ const version = (this.manifest && this.manifest.setup && this.manifest.setup.version) || 0
3386
+ return dismissed >= version
3387
+ }
3388
+ return false
3389
+ },
3390
+
3391
+ /**
3392
+ * The setup action that records a closed wizard on the server, from
3393
+ * `manifest.setup.dismissAction`, or '' when the app declares none.
3394
+ *
3395
+ * @spec openspec/changes/setup-wizard-close-on-server/specs/cn-setup-wizard/spec.md
3396
+ * @return {string} The action id.
3397
+ */
3398
+ setupDismissAction() {
3399
+ const action = this.manifest && this.manifest.setup && this.manifest.setup.dismissAction
3400
+ return typeof action === 'string' && /^[a-z0-9][a-z0-9-]*$/.test(action) ? action : ''
3401
+ },
3402
+
3403
+ /**
3404
+ * Record the setup wizard as closed for this manifest `setup.version`
3405
+ * so it doesn't auto-open again: in this browser (localStorage) and,
3406
+ * when the manifest declares `setup.dismissAction`, on the server
3407
+ * through the app's own setup action endpoint
3408
+ * (`POST /apps/{appId}/api/setup/action/{dismissAction}`, admin-only
3409
+ * like every setup endpoint, so the record is per instance). Then
3410
+ * emits `setup-wizard-dismissed`. Once per page load: finishing
3411
+ * records it, and the result screen's Close that follows does not
3412
+ * post again.
3341
3413
  *
3414
+ * @spec openspec/changes/setup-wizard-close-on-server/specs/cn-setup-wizard/spec.md
3415
+ * @param {boolean} [finished] True when the user finished the wizard, false when they closed it.
3342
3416
  * @return {void}
3343
3417
  */
3344
- persistSetupWizardDismissal() {
3418
+ persistSetupWizardDismissal(finished = false) {
3345
3419
  try {
3346
3420
  window.localStorage.setItem(this.setupWizardDismissKey(), '1')
3347
3421
  } catch {
3348
3422
  // Best-effort persistence (private mode / no storage).
3349
3423
  }
3424
+ if (this.setupWizardDismissed) {
3425
+ return
3426
+ }
3350
3427
  this.setupWizardDismissed = true
3428
+ const action = this.setupDismissAction()
3429
+ if (action) {
3430
+ try {
3431
+ Promise.resolve(axios.post(generateUrl(`/apps/${this.appId}/api/setup/action/${action}`), { finished: finished === true }))
3432
+ .catch(() => { /* the local record still holds in this browser */ })
3433
+ } catch {
3434
+ // Never let a failed record break the close.
3435
+ }
3436
+ }
3437
+ /**
3438
+ * @event setup-wizard-dismissed Emitted once when the user closes or finishes the setup wizard.
3439
+ * @type {{ appId: string, version: number, finished: boolean }}
3440
+ */
3441
+ this.$emit('setup-wizard-dismissed', {
3442
+ appId: this.appId,
3443
+ version: (this.manifest && this.manifest.setup && this.manifest.setup.version) || 0,
3444
+ finished: finished === true,
3445
+ })
3351
3446
  },
3352
3447
 
3353
3448
  /**
@@ -3434,6 +3529,49 @@ export default {
3434
3529
  persistWalkthroughProgress(this.appId, this.walkthroughConfigKey, value)
3435
3530
  },
3436
3531
 
3532
+ /**
3533
+ * Remember that the user paused the tour, at the step they were on,
3534
+ * so the next page load keeps it hidden until they pick "Continue".
3535
+ *
3536
+ * @spec openspec/changes/audit-round-lib-fixes/specs/cn-walkthrough/spec.md
3537
+ * @param {object} [where] `{ tourId, stepId, index }` from CnWalkthrough.
3538
+ * @return {void}
3539
+ */
3540
+ onWalkthroughPause(where) {
3541
+ const current = this.walkthroughProgressValue || {}
3542
+ const tourId = (where && where.tourId) || current.tourId
3543
+ if (!tourId) {
3544
+ return
3545
+ }
3546
+ const value = {
3547
+ tourId,
3548
+ stepId: (where && where.tourId) ? (where.stepId || '') : (current.stepId || ''),
3549
+ index: (where && where.tourId && Number.isInteger(where.index)) ? where.index : (current.index || 0),
3550
+ version: String((this.manifest && this.manifest.version) || ''),
3551
+ paused: true,
3552
+ }
3553
+ this.walkthroughProgressValue = value
3554
+ persistWalkthroughProgress(this.appId, this.walkthroughConfigKey, value)
3555
+ },
3556
+
3557
+ /**
3558
+ * Clear the paused mark when the user continues the tour. The step
3559
+ * stays; the next step change writes fresh progress anyway.
3560
+ *
3561
+ * @spec openspec/changes/audit-round-lib-fixes/specs/cn-walkthrough/spec.md
3562
+ * @return {void}
3563
+ */
3564
+ unpauseWalkthroughProgress() {
3565
+ const progress = this.walkthroughProgressValue
3566
+ if (!progress || !progress.paused) {
3567
+ return
3568
+ }
3569
+ const value = { ...progress }
3570
+ delete value.paused
3571
+ this.walkthroughProgressValue = value
3572
+ persistWalkthroughProgress(this.appId, this.walkthroughConfigKey, value)
3573
+ },
3574
+
3437
3575
  /**
3438
3576
  * Continue an unfinished tour from the user-settings dialog: show a
3439
3577
  * paused tour again, or start the remembered tour at its step.
@@ -3448,10 +3586,11 @@ export default {
3448
3586
  }
3449
3587
  setTimeout(() => {
3450
3588
  const wt = useWalkthrough(this.appId, this.manifest)
3589
+ const progress = this.walkthroughProgressValue
3590
+ this.unpauseWalkthroughProgress()
3451
3591
  if (wt.resumePaused()) {
3452
3592
  return
3453
3593
  }
3454
- const progress = this.walkthroughProgressValue
3455
3594
  if (progress) {
3456
3595
  wt.resumeAt(progress.tourId, progress.stepId)
3457
3596
  }
@@ -3478,6 +3617,7 @@ export default {
3478
3617
  setTimeout(() => {
3479
3618
  const id = this.manifest.walkthrough.tours[0] && this.manifest.walkthrough.tours[0].id
3480
3619
  if (id) {
3620
+ this.unpauseWalkthroughProgress()
3481
3621
  useWalkthrough(this.appId, this.manifest).restart(id)
3482
3622
  }
3483
3623
  }, 50)
@@ -330,24 +330,26 @@
330
330
  v-if="missingRequiredApp(item)"
331
331
  :title="getWidgetTitle(item)"
332
332
  :showTitle="widgetShowTitle(item)">
333
- <NcEmptyContent
334
- :name="installAppLabel(missingRequiredApp(item))"
335
- :description="t('nextcloud-vue', 'This widget shows data from another app that isn\'t installed yet.')"
336
- class="cn-dashboard-page__requires-app cn-requires-app">
337
- <template #icon>
338
- <Download :size="24" />
339
- </template>
340
- <template #action>
341
- <NcButton
342
- variant="primary"
343
- :href="appInstallUrl(missingRequiredApp(item))">
344
- <template #icon>
345
- <Download :size="18" />
346
- </template>
347
- {{ installAppLabel(missingRequiredApp(item)) }}
348
- </NcButton>
349
- </template>
350
- </NcEmptyContent>
333
+ <!-- Compact on purpose: a two-row tile is about 150px, less
334
+ a title row. NcEmptyContent's 64px icon, 20px name,
335
+ description and button did not fit, so the icon ran out
336
+ at the top and the button at the bottom. The button
337
+ already says "Install {app}", so there is no separate
338
+ name, and the text keeps to two lines. -->
339
+ <div class="cn-dashboard-page__requires-app cn-requires-app cn-requires-app--compact"
340
+ data-testid="cn-requires-app">
341
+ <p class="cn-requires-app__description">
342
+ {{ t('nextcloud-vue', 'This widget shows data from another app that isn\'t installed yet.') }}
343
+ </p>
344
+ <NcButton
345
+ variant="primary"
346
+ :href="appInstallUrl(missingRequiredApp(item))">
347
+ <template #icon>
348
+ <Download :size="18" />
349
+ </template>
350
+ {{ installAppLabel(missingRequiredApp(item)) }}
351
+ </NcButton>
352
+ </div>
351
353
  </CnWidgetWrapper>
352
354
 
353
355
  <!-- Tile widget -->
@@ -226,6 +226,10 @@
226
226
  <strong>{{ step.transition }}</strong>
227
227
  <span class="cn-run-sidebar__hint"> · {{ step.status }}</span>
228
228
  <span v-if="step.error" class="cn-run-sidebar__error"> · {{ step.error }}</span>
229
+ <!-- A send step's outcome lists, by name: who got it, who
230
+ opted out, who the opt-out check could not answer for. -->
231
+ <CnFlowStepOutcomes v-if="step.report && step.report.messaging"
232
+ :report="step.report.messaging" />
229
233
  </li>
230
234
  </ol>
231
235
  </component>
@@ -241,6 +245,7 @@ import CheckboxMarkedOutline from 'vue-material-design-icons/CheckboxMarkedOutli
241
245
  import DatabaseOutline from 'vue-material-design-icons/DatabaseOutline.vue'
242
246
  import FormatListBulleted from 'vue-material-design-icons/FormatListBulleted.vue'
243
247
  import Replay from 'vue-material-design-icons/Replay.vue'
248
+ import CnFlowStepOutcomes from '../CnFlowStepOutcomes/CnFlowStepOutcomes.vue'
244
249
  import { FLOW_RUN_ACTIVE_STATUSES, useFlowStore } from '../../composables/useFlowStore.js'
245
250
 
246
251
  export default {
@@ -249,6 +254,7 @@ export default {
249
254
  components: {
250
255
  ArrowLeft,
251
256
  CheckboxMarkedOutline,
257
+ CnFlowStepOutcomes,
252
258
  DatabaseOutline,
253
259
  FormatListBulleted,
254
260
  NcAppSidebarTab,