@conduction/nextcloud-vue 2.69.0 → 2.70.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 (121) hide show
  1. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js +25 -0
  2. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
  3. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +155 -152
  4. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
  5. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +77 -0
  6. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
  7. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +8 -8
  8. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
  9. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue.js +1 -0
  10. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue.js.map +1 -1
  11. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue2.js +28 -0
  12. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue2.js.map +1 -1
  13. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue3.js +4 -2
  14. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue3.js.map +1 -1
  15. package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue2.js +25 -1
  16. package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue2.js.map +1 -1
  17. package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue3.js +32 -17
  18. package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue3.js.map +1 -1
  19. package/dist/esm/components/CnCapabilityTable/CnCapabilityTable.vue2.js +14 -1
  20. package/dist/esm/components/CnCapabilityTable/CnCapabilityTable.vue2.js.map +1 -1
  21. package/dist/esm/components/CnCapabilityTable/CnCapabilityTable.vue3.js.map +1 -1
  22. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js +43 -2
  23. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js.map +1 -1
  24. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js +3 -3
  25. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js.map +1 -1
  26. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +25 -1
  27. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  28. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +2 -1
  29. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  30. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +35 -1
  31. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  32. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  33. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +54 -3
  34. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  35. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +6 -3
  36. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  37. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +28 -0
  38. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  39. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +3 -2
  40. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  41. package/dist/esm/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue2.js +12 -0
  42. package/dist/esm/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue2.js.map +1 -1
  43. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js +9 -0
  44. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js.map +1 -1
  45. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js.map +1 -1
  46. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js +54 -7
  47. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js.map +1 -1
  48. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js +26 -8
  49. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js.map +1 -1
  50. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +143 -0
  51. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  52. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +68 -25
  53. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  54. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue2.js.map +1 -1
  55. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue3.js.map +1 -1
  56. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +4 -1
  57. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  58. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  59. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js +17 -3
  60. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js.map +1 -1
  61. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js +25 -24
  62. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js.map +1 -1
  63. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js.map +1 -1
  64. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js.map +1 -1
  65. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +27 -0
  66. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
  67. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +7 -0
  68. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
  69. package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue2.js +24 -1
  70. package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue2.js.map +1 -1
  71. package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue3.js +1 -1
  72. package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue3.js.map +1 -1
  73. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js +11 -0
  74. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js.map +1 -1
  75. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js +4 -2
  76. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js.map +1 -1
  77. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js +15 -2
  78. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js.map +1 -1
  79. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js +31 -32
  80. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js.map +1 -1
  81. package/dist/esm/nextcloud-vue.css +145 -0
  82. package/dist/esm/utils/capabilityComparison.js +29 -1
  83. package/dist/esm/utils/capabilityComparison.js.map +1 -1
  84. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  85. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  86. package/dist/nextcloud-vue.cjs.js +2167 -1399
  87. package/dist/nextcloud-vue.cjs.js.map +2 -2
  88. package/dist/nextcloud-vue.css +145 -0
  89. package/package.json +1 -1
  90. package/src/components/CnActionsBar/CnActionsBar.md +2 -0
  91. package/src/components/CnActionsBar/CnActionsBar.vue +28 -2
  92. package/src/components/CnAppNav/CnAppNav.vue +85 -8
  93. package/src/components/CnBreadcrumbs/CnBreadcrumbs.vue +45 -0
  94. package/src/components/CnCalendarWidget/CnCalendarWidget.vue +32 -4
  95. package/src/components/CnCapabilityTable/CnCapabilityTable.vue +14 -0
  96. package/src/components/CnDashboardGrid/CnDashboardGrid.md +2 -0
  97. package/src/components/CnDashboardGrid/CnDashboardGrid.vue +54 -2
  98. package/src/components/CnDashboardPage/CnDashboardPage.vue +26 -1
  99. package/src/components/CnDataTable/CnDataTable.vue +35 -1
  100. package/src/components/CnDetailPage/CnDetailPage.md +2 -0
  101. package/src/components/CnDetailPage/CnDetailPage.vue +57 -3
  102. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +30 -1
  103. package/src/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue +12 -0
  104. package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +9 -0
  105. package/src/components/CnHeaderWidget/CnHeaderWidget.vue +95 -8
  106. package/src/components/CnIndexPage/CnIndexPage.vue +171 -6
  107. package/src/components/CnNextStepCard/CnNextStepCard.vue +11 -0
  108. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +4 -1
  109. package/src/components/CnStackedBarWidget/CnStackedBarWidget.vue +34 -7
  110. package/src/components/CnStagesWidget/CnStagesWidget.vue +19 -0
  111. package/src/components/CnStatWidget/CnStatWidget.vue +36 -0
  112. package/src/components/CnStatsBlock/CnStatsBlock.md +2 -0
  113. package/src/components/CnStatsBlock/CnStatsBlock.vue +25 -2
  114. package/src/components/CnStatsBlockWidget/CnStatsBlockWidget.vue +13 -0
  115. package/src/components/CnWeekStripWidget/CnWeekStripWidget.vue +24 -3
  116. package/src/css/detail-page.css +10 -0
  117. package/src/css/index-page.css +12 -0
  118. package/src/css/kpi-card.css +15 -0
  119. package/src/schemas/app-manifest-v2.schema.json +46 -5
  120. package/src/utils/capabilityComparison.js +28 -0
  121. package/src/utils/validateManifestV2.compiled.js +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"CnFeaturesAndRoadmapPage.vue2.js","sources":["../../../../src/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue"],"sourcesContent":["<!--\n CnFeaturesAndRoadmapPage — Manifest-driven Features & Roadmap surface.\n\n v2 manifest page primitive for `type: \"roadmap\"`. Wraps\n `CnFeaturesAndRoadmapView` so consuming apps can declare\n `{ id: \"FeaturesRoadmap\", route: \"/features-roadmap\", type: \"roadmap\" }`\n in their manifest without writing a per-app wrapper that just\n unpacks initialState into the view's three props.\n\n Resolution order for each of the three values:\n\n 1. Explicit `pages[].config.<key>` from the manifest.\n 2. `loadState(appId, 'features_roadmap_<key>', <fallback>)`.\n 3. Hardcoded fallback (`repo` → `ConductionNL/<appId>` on GitHub;\n `forge` → the `cnFeatureRequestForge` inject, else DEFAULT_FORGE).\n\n `appId` comes from the `cnAiContext` inject that CnAppRoot already\n provides; consumers can override it explicitly via the prop for tests.\n\n Spec: ConductionNL/nextcloud-vue#264.\n-->\n<template>\n\t<CnFeaturesAndRoadmapView\n\t\t:repo=\"resolvedRepo\"\n\t\t:forge=\"resolvedForge\"\n\t\t:features=\"resolvedFeatures\"\n\t\t:disabled=\"resolvedDisabled\"\n\t\t:openbuiltUrl=\"resolvedOpenbuiltUrl\"\n\t\t:llmSkillsUrl=\"resolvedLlmSkillsUrl\"\n\t\t:suggestUrl=\"resolvedSuggestUrl\"\n\t\t:documentationUrl=\"resolvedDocumentationUrl\"\n\t\t:capabilityComparison=\"resolvedCapabilityComparison\" />\n</template>\n\n<script>\n// `@nextcloud/initial-state` is a DECLARED (non-optional) peer dependency, so\n// it is imported statically — a lazy CommonJS resolution can never succeed from\n// `dist/esm/**` (the package's `exports` map is import-only and the ESM build\n// has no such function), which meant this helper used to return its fallback\n// unconditionally.\nimport { loadState } from '@nextcloud/initial-state'\nimport CnFeaturesAndRoadmapView from '../CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue'\nimport { DEFAULT_FORGE } from '../../utils/forge.js'\n\n/**\n * Read a key from `@nextcloud/initial-state`, tolerating an unprovisioned\n * slot (mirrors resolveManifestSentinels.js).\n *\n * @param {string} appId Nextcloud app ID.\n * @param {string} key Initial-state key (full key, not prefixed).\n * @param {unknown} fallback Default value when no provisioned slot exists.\n * @return {unknown} Provisioned value or the fallback.\n */\nfunction readInitialState(appId, key, fallback) {\n\ttry {\n\t\t// `loadState` throws only when the `#initial-state-*` element is absent\n\t\t// AND no fallback is given; a fallback IS given here, so this catch\n\t\t// covers the parse-error case only. It is a runtime condition, never a\n\t\t// module-resolution failure.\n\t\treturn loadState(appId, key, fallback)\n\t} catch {\n\t\t// Slot present but unparseable — fall back.\n\t}\n\treturn fallback\n}\n\nexport default {\n\tname: 'CnFeaturesAndRoadmapPage',\n\n\tcomponents: {\n\t\tCnFeaturesAndRoadmapView,\n\t},\n\n\tinject: {\n\t\tcnAiContext: {\n\t\t\tdefault: () => ({ appId: 'unknown' }),\n\t\t},\n\n\t\t/**\n\t\t * Forge config provided by CnAppRoot from `manifest.nav.forge`.\n\t\t * Used as the fallback for `resolvedForge` so the whole app shares\n\t\t * one forge setting. Defaults to Codeberg when no ancestor.\n\t\t */\n\t\tcnFeatureRequestForge: {\n\t\t\tdefault: () => ({ ...DEFAULT_FORGE }),\n\t\t},\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * `<owner>/<repo>` of the app's repository on the forge. When\n\t\t * omitted, falls back to the loadState value, then to\n\t\t * `ConductionNL/<appId>` — the fleet convention on GitHub.\n\t\t */\n\t\trepo: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Target forge for the feature-request deep-link. Manifest config\n\t\t * > initialState > the `cnFeatureRequestForge` inject (CnAppRoot)\n\t\t * > Codeberg.\n\t\t *\n\t\t * @type {{type: 'codeberg'|'forgejo'|'gitea'|'github', baseUrl?: string}|null}\n\t\t */\n\t\tforge: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Build-time feature manifest. When omitted, falls back to\n\t\t * the loadState value, then to an empty array.\n\t\t *\n\t\t * @type {Array<{slug: string, title: string, summary: string, docsUrl: string}>}\n\t\t */\n\t\tfeatures: {\n\t\t\ttype: Array,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Admin opt-out flag. When omitted, falls back to the\n\t\t * loadState value, then to false.\n\t\t *\n\t\t * @type {boolean|null}\n\t\t */\n\t\tdisabled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Override for the OpenBuilt sidebar CTA target. Manifest config\n\t\t * > initialState > the view's own default (in-instance /apps/openbuilt).\n\t\t */\n\t\topenbuiltUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Override for the LLM-skills sidebar CTA target. Manifest config\n\t\t * > initialState > docs.conduction.nl/ai-skills.\n\t\t */\n\t\tllmSkillsUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Override for the Suggest sidebar CTA. When set, the sidebar\n\t\t * Suggest CTA renders as an anchor pointing here; when empty\n\t\t * (default) the view derives the forge's feature-request issue\n\t\t * form URL. Manifest config > initialState > '' (forge form).\n\t\t */\n\t\tsuggestUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * URL of the app's public documentation site. When set, an info\n\t\t * banner above the card grid points users at the docs. Manifest\n\t\t * config > initialState > '' (no banner).\n\t\t */\n\t\tdocumentationUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Optional capability comparison document, forwarded to the view.\n\t\t * Manifest config > initialState (`features_roadmap_capabilities`) >\n\t\t * null. When null the view renders its two stops as it always has.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tcapabilityComparison: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Override for the appId used to namespace loadState lookups.\n\t\t * Tests pass an explicit value; production reads it from the\n\t\t * `cnAiContext` inject populated by CnAppRoot.\n\t\t */\n\t\tappId: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Effective app id used to namespace `loadState` calls.\n\t\t * Prop takes precedence over the cnAiContext inject so that\n\t\t * unit tests can pass a known id without mocking the inject.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\teffectiveAppId() {\n\t\t\treturn this.appId || this.cnAiContext?.appId || 'unknown'\n\t\t},\n\n\t\t/**\n\t\t * Effective repo. Manifest config > initialState > fallback.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tresolvedRepo() {\n\t\t\tif (this.repo) {\n\t\t\t\treturn this.repo\n\t\t\t}\n\t\t\treturn readInitialState(\n\t\t\t\tthis.effectiveAppId,\n\t\t\t\t'features_roadmap_repo',\n\t\t\t\t`ConductionNL/${this.effectiveAppId}`,\n\t\t\t)\n\t\t},\n\n\t\t/**\n\t\t * Effective forge. Manifest config > initialState >\n\t\t * `cnFeatureRequestForge` inject (CnAppRoot) > DEFAULT_FORGE.\n\t\t *\n\t\t * @return {{type: string, baseUrl?: string}}\n\t\t */\n\t\tresolvedForge() {\n\t\t\tif (this.forge) {\n\t\t\t\treturn this.forge\n\t\t\t}\n\t\t\treturn readInitialState(\n\t\t\t\tthis.effectiveAppId,\n\t\t\t\t'features_roadmap_forge',\n\t\t\t\tthis.cnFeatureRequestForge || { ...DEFAULT_FORGE },\n\t\t\t)\n\t\t},\n\n\t\t/**\n\t\t * Effective features list. Manifest config > initialState > [].\n\t\t *\n\t\t * @return {Array}\n\t\t */\n\t\tresolvedFeatures() {\n\t\t\tif (this.features !== null) {\n\t\t\t\treturn this.features\n\t\t\t}\n\t\t\treturn readInitialState(this.effectiveAppId, 'features_roadmap_features', [])\n\t\t},\n\n\t\t/**\n\t\t * Effective disabled flag. Manifest config > initialState > false.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tresolvedDisabled() {\n\t\t\tif (this.disabled !== null) {\n\t\t\t\treturn this.disabled\n\t\t\t}\n\t\t\treturn readInitialState(this.effectiveAppId, 'features_roadmap_disabled', false)\n\t\t},\n\n\t\tresolvedOpenbuiltUrl() {\n\t\t\treturn this.openbuiltUrl\n\t\t\t\t|| readInitialState(this.effectiveAppId, 'features_roadmap_openbuilt_url', '')\n\t\t},\n\n\t\tresolvedLlmSkillsUrl() {\n\t\t\treturn this.llmSkillsUrl\n\t\t\t\t|| readInitialState(this.effectiveAppId, 'features_roadmap_llm_skills_url', '')\n\t\t},\n\n\t\tresolvedSuggestUrl() {\n\t\t\treturn this.suggestUrl\n\t\t\t\t|| readInitialState(this.effectiveAppId, 'features_roadmap_suggest_url', '')\n\t\t},\n\n\t\tresolvedDocumentationUrl() {\n\t\t\treturn this.documentationUrl\n\t\t\t\t|| readInitialState(this.effectiveAppId, 'features_roadmap_documentation_url', '')\n\t\t},\n\n\t\t/**\n\t\t * Effective capability comparison. Manifest config > initialState >\n\t\t * null.\n\t\t *\n\t\t * `null` and not `{}`: an empty object would make the view believe a\n\t\t * comparison exists and grow a third toggle stop over zero rows.\n\t\t *\n\t\t * @return {object|null}\n\t\t */\n\t\tresolvedCapabilityComparison() {\n\t\t\tif (this.capabilityComparison !== null) {\n\t\t\t\treturn this.capabilityComparison\n\t\t\t}\n\t\t\treturn readInitialState(this.effectiveAppId, 'features_roadmap_capabilities', null)\n\t\t},\n\t},\n}\n</script>\n"],"names":["CnFeaturesAndRoadmapView"],"mappings":";;;;AA4CA;AACC;AACA;AACA;AACA;;AAEA;;;AAGD,SAAS,gBAAgB,CAAC,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE;AAC/C,CAAA,IAAI;AACH;;;;AAIA,EAAA,OAAO,SAAS,CAAC,KAAK,EAAE,GAAG,EAAE,QAAQ,CAAA;AACtC,EAAA,CAAE,MAAM;AACP;AACD,EAAA;CACA,OAAO,QAAO;AACf,CAAA;;AAEA,aAAe;AACd,CAAA,IAAI,EAAE,0BAA0B;;CAEhC,UAAU,EAAE;4BACXA,UAAwB;EACxB;;AAED,CAAA,MAAM,EAAE;EACP,WAAW,EAAE;AACZ,GAAA,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,SAAQ,EAAG,CAAC;GACrC;;AAED;;;;;EAKA,qBAAqB,EAAE;AACtB,GAAA,OAAO,EAAE,OAAO,EAAE,GAAG,aAAY,EAAG,CAAC;GACrC;EACD;;AAED,CAAA,KAAK,EAAE;AACN;;AAEC;;;EAGD,IAAI,EAAE;GACL,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;;;AAIC;AACA;;AAED,EAAA,KAAK,EAAE;GACN,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,IAAI;GACb;;AAED;;AAEC;AACA;AACA;;AAED,EAAA,QAAQ,EAAE;GACT,IAAI,EAAE,KAAK;GACX,OAAO,EAAE,IAAI;GACb;;AAED;AACC;;AAEA;AACA;;AAED,EAAA,QAAQ,EAAE;GACT,IAAI,EAAE,OAAO;GACb,OAAO,EAAE,IAAI;GACb;;AAED;;AAEC;;EAED,YAAY,EAAE;GACb,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;AAEC;;EAED,YAAY,EAAE;GACb,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;;;;;EAMA,UAAU,EAAE;GACX,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;;AAGC;;AAED,EAAA,gBAAgB,EAAE;GACjB,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;;;AAIC;AACA;;AAED,EAAA,oBAAoB,EAAE;GACrB,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,IAAI;GACb;;AAED;;;AAGC;;AAED,EAAA,KAAK,EAAE;GACN,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;EACD;;AAED,CAAA,QAAQ,EAAE;AACT;;;;AAIC;AACA;;AAED,EAAA,cAAc,GAAG;GAChB,OAAO,IAAI,CAAC,SAAS,IAAI,CAAC,WAAW,EAAE,KAAI,IAAK,SAAQ;GACxD;;AAED;;AAEC;AACA;;AAED,EAAA,YAAY,GAAG;GACd,IAAI,IAAI,CAAC,IAAI,EAAE;AACd,IAAA,OAAO,IAAI,CAAC,IAAG;AAChB,IAAA;GACA,OAAO,gBAAgB;AACtB,IAAA,IAAI,CAAC,cAAc;IACnB,uBAAuB;AACvB,IAAA,CAAC,aAAa,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC;AACtC,IAAA;GACA;;AAED;AACC;;AAEA;AACA;;AAED,EAAA,aAAa,GAAG;AACf,GAAA,IAAI,IAAI,CAAC,KAAK,EAAE;AACf,IAAA,OAAO,IAAI,CAAC,KAAI;AACjB,IAAA;GACA,OAAO,gBAAgB;AACtB,IAAA,IAAI,CAAC,cAAc;IACnB,wBAAwB;IACxB,IAAI,CAAC,qBAAoB,IAAK,EAAE,GAAG,eAAe;AACnD,IAAA;GACA;;AAED;;AAEC;AACA;;AAED,EAAA,gBAAgB,GAAG;GAClB,IAAI,IAAI,CAAC,aAAa,IAAI,EAAE;AAC3B,IAAA,OAAO,IAAI,CAAC,QAAO;AACpB,IAAA;AACA,GAAA,OAAO,gBAAgB,CAAC,IAAI,CAAC,cAAc,EAAE,2BAA2B,EAAE,EAAE,CAAA;GAC5E;;AAED;;AAEC;AACA;;AAED,EAAA,gBAAgB,GAAG;GAClB,IAAI,IAAI,CAAC,aAAa,IAAI,EAAE;AAC3B,IAAA,OAAO,IAAI,CAAC,QAAO;AACpB,IAAA;AACA,GAAA,OAAO,gBAAgB,CAAC,IAAI,CAAC,cAAc,EAAE,2BAA2B,EAAE,KAAK,CAAA;GAC/E;;EAED,oBAAoB,GAAG;GACtB,OAAO,IAAI,CAAC,YAAW;AACtB,OAAG,gBAAgB,CAAC,IAAI,CAAC,cAAc,EAAE,gCAAgC,EAAE,EAAE,CAAA;GAC9E;;EAED,oBAAoB,GAAG;GACtB,OAAO,IAAI,CAAC,YAAW;AACtB,OAAG,gBAAgB,CAAC,IAAI,CAAC,cAAc,EAAE,iCAAiC,EAAE,EAAE,CAAA;GAC/E;;AAED,EAAA,kBAAkB,GAAG;AACpB,GAAA,OAAO,IAAI,CAAC,UAAS;AACpB,OAAG,gBAAgB,CAAC,IAAI,CAAC,cAAc,EAAE,8BAA8B,EAAE,EAAE,CAAA;GAC5E;;EAED,wBAAwB,GAAG;GAC1B,OAAO,IAAI,CAAC,gBAAe;AAC1B,OAAG,gBAAgB,CAAC,IAAI,CAAC,cAAc,EAAE,oCAAoC,EAAE,EAAE,CAAA;GAClF;;AAED;;;AAGC;;;AAGA;AACA;;AAED,EAAA,4BAA4B,GAAG;AAC9B,GAAA,IAAI,IAAI,CAAC,oBAAqB,KAAI,IAAI,EAAE;AACvC,IAAA,OAAO,IAAI,CAAC,oBAAmB;AAChC,IAAA;AACA,GAAA,OAAO,gBAAgB,CAAC,IAAI,CAAC,cAAc,EAAE,+BAA+B,EAAE,IAAI,CAAA;GAClF;EACD;AACF;;;;"}
1
+ {"version":3,"file":"CnFeaturesAndRoadmapPage.vue2.js","sources":["../../../../src/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue"],"sourcesContent":["<!--\n CnFeaturesAndRoadmapPage — Manifest-driven Features & Roadmap surface.\n\n v2 manifest page primitive for `type: \"roadmap\"`. Wraps\n `CnFeaturesAndRoadmapView` so consuming apps can declare\n `{ id: \"FeaturesRoadmap\", route: \"/features-roadmap\", type: \"roadmap\" }`\n in their manifest without writing a per-app wrapper that just\n unpacks initialState into the view's three props.\n\n Resolution order for each of the three values:\n\n 1. Explicit `pages[].config.<key>` from the manifest.\n 2. `loadState(appId, 'features_roadmap_<key>', <fallback>)`.\n 3. Hardcoded fallback (`repo` → `ConductionNL/<appId>` on GitHub;\n `forge` → the `cnFeatureRequestForge` inject, else DEFAULT_FORGE).\n\n `appId` comes from the `cnAiContext` inject that CnAppRoot already\n provides; consumers can override it explicitly via the prop for tests.\n\n Spec: ConductionNL/nextcloud-vue#264.\n-->\n<template>\n\t<CnFeaturesAndRoadmapView\n\t\t:repo=\"resolvedRepo\"\n\t\t:forge=\"resolvedForge\"\n\t\t:features=\"resolvedFeatures\"\n\t\t:disabled=\"resolvedDisabled\"\n\t\t:openbuiltUrl=\"resolvedOpenbuiltUrl\"\n\t\t:llmSkillsUrl=\"resolvedLlmSkillsUrl\"\n\t\t:suggestUrl=\"resolvedSuggestUrl\"\n\t\t:documentationUrl=\"resolvedDocumentationUrl\"\n\t\t:capabilityComparison=\"resolvedCapabilityComparison\" />\n</template>\n\n<script>\n// `@nextcloud/initial-state` is a DECLARED (non-optional) peer dependency, so\n// it is imported statically — a lazy CommonJS resolution can never succeed from\n// `dist/esm/**` (the package's `exports` map is import-only and the ESM build\n// has no such function), which meant this helper used to return its fallback\n// unconditionally.\nimport { loadState } from '@nextcloud/initial-state'\nimport CnFeaturesAndRoadmapView from '../CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue'\nimport { warnCapabilityComparisonDeprecated } from '../../utils/capabilityComparison.js'\nimport { DEFAULT_FORGE } from '../../utils/forge.js'\n\n/**\n * Read a key from `@nextcloud/initial-state`, tolerating an unprovisioned\n * slot (mirrors resolveManifestSentinels.js).\n *\n * @param {string} appId Nextcloud app ID.\n * @param {string} key Initial-state key (full key, not prefixed).\n * @param {unknown} fallback Default value when no provisioned slot exists.\n * @return {unknown} Provisioned value or the fallback.\n */\nfunction readInitialState(appId, key, fallback) {\n\ttry {\n\t\t// `loadState` throws only when the `#initial-state-*` element is absent\n\t\t// AND no fallback is given; a fallback IS given here, so this catch\n\t\t// covers the parse-error case only. It is a runtime condition, never a\n\t\t// module-resolution failure.\n\t\treturn loadState(appId, key, fallback)\n\t} catch {\n\t\t// Slot present but unparseable — fall back.\n\t}\n\treturn fallback\n}\n\nexport default {\n\tname: 'CnFeaturesAndRoadmapPage',\n\n\tcomponents: {\n\t\tCnFeaturesAndRoadmapView,\n\t},\n\n\tinject: {\n\t\tcnAiContext: {\n\t\t\tdefault: () => ({ appId: 'unknown' }),\n\t\t},\n\n\t\t/**\n\t\t * Forge config provided by CnAppRoot from `manifest.nav.forge`.\n\t\t * Used as the fallback for `resolvedForge` so the whole app shares\n\t\t * one forge setting. Defaults to Codeberg when no ancestor.\n\t\t */\n\t\tcnFeatureRequestForge: {\n\t\t\tdefault: () => ({ ...DEFAULT_FORGE }),\n\t\t},\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * `<owner>/<repo>` of the app's repository on the forge. When\n\t\t * omitted, falls back to the loadState value, then to\n\t\t * `ConductionNL/<appId>` — the fleet convention on GitHub.\n\t\t */\n\t\trepo: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Target forge for the feature-request deep-link. Manifest config\n\t\t * > initialState > the `cnFeatureRequestForge` inject (CnAppRoot)\n\t\t * > Codeberg.\n\t\t *\n\t\t * @type {{type: 'codeberg'|'forgejo'|'gitea'|'github', baseUrl?: string}|null}\n\t\t */\n\t\tforge: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Build-time feature manifest. When omitted, falls back to\n\t\t * the loadState value, then to an empty array.\n\t\t *\n\t\t * @type {Array<{slug: string, title: string, summary: string, docsUrl: string}>}\n\t\t */\n\t\tfeatures: {\n\t\t\ttype: Array,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Admin opt-out flag. When omitted, falls back to the\n\t\t * loadState value, then to false.\n\t\t *\n\t\t * @type {boolean|null}\n\t\t */\n\t\tdisabled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Override for the OpenBuilt sidebar CTA target. Manifest config\n\t\t * > initialState > the view's own default (in-instance /apps/openbuilt).\n\t\t */\n\t\topenbuiltUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Override for the LLM-skills sidebar CTA target. Manifest config\n\t\t * > initialState > docs.conduction.nl/ai-skills.\n\t\t */\n\t\tllmSkillsUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Override for the Suggest sidebar CTA. When set, the sidebar\n\t\t * Suggest CTA renders as an anchor pointing here; when empty\n\t\t * (default) the view derives the forge's feature-request issue\n\t\t * form URL. Manifest config > initialState > '' (forge form).\n\t\t */\n\t\tsuggestUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * URL of the app's public documentation site. When set, an info\n\t\t * banner above the card grid points users at the docs. Manifest\n\t\t * config > initialState > '' (no banner).\n\t\t */\n\t\tdocumentationUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Optional capability comparison document, forwarded to the view.\n\t\t * Manifest config > initialState (`features_roadmap_capabilities`) >\n\t\t * null. When null the view renders its two stops as it always has.\n\t\t *\n\t\t * Deprecated: publish the comparison on the app's docs site and link to it (as dossiq #3312 and pipelinq #2195 did). Still rendered; removal waits for a major version. The same goes for the `features_roadmap_capabilities` initial\n\t\t * state key. A development build warns once when either is set.\n\t\t *\n\t\t * @deprecated since 2.66.0, publish the comparison on the app's docs site and link to it.\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-the-in-app-capability-comparison-is-deprecated\n\t\t * @type {object|null}\n\t\t */\n\t\tcapabilityComparison: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Override for the appId used to namespace loadState lookups.\n\t\t * Tests pass an explicit value; production reads it from the\n\t\t * `cnAiContext` inject populated by CnAppRoot.\n\t\t */\n\t\tappId: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Effective app id used to namespace `loadState` calls.\n\t\t * Prop takes precedence over the cnAiContext inject so that\n\t\t * unit tests can pass a known id without mocking the inject.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\teffectiveAppId() {\n\t\t\treturn this.appId || this.cnAiContext?.appId || 'unknown'\n\t\t},\n\n\t\t/**\n\t\t * Effective repo. Manifest config > initialState > fallback.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tresolvedRepo() {\n\t\t\tif (this.repo) {\n\t\t\t\treturn this.repo\n\t\t\t}\n\t\t\treturn readInitialState(\n\t\t\t\tthis.effectiveAppId,\n\t\t\t\t'features_roadmap_repo',\n\t\t\t\t`ConductionNL/${this.effectiveAppId}`,\n\t\t\t)\n\t\t},\n\n\t\t/**\n\t\t * Effective forge. Manifest config > initialState >\n\t\t * `cnFeatureRequestForge` inject (CnAppRoot) > DEFAULT_FORGE.\n\t\t *\n\t\t * @return {{type: string, baseUrl?: string}}\n\t\t */\n\t\tresolvedForge() {\n\t\t\tif (this.forge) {\n\t\t\t\treturn this.forge\n\t\t\t}\n\t\t\treturn readInitialState(\n\t\t\t\tthis.effectiveAppId,\n\t\t\t\t'features_roadmap_forge',\n\t\t\t\tthis.cnFeatureRequestForge || { ...DEFAULT_FORGE },\n\t\t\t)\n\t\t},\n\n\t\t/**\n\t\t * Effective features list. Manifest config > initialState > [].\n\t\t *\n\t\t * @return {Array}\n\t\t */\n\t\tresolvedFeatures() {\n\t\t\tif (this.features !== null) {\n\t\t\t\treturn this.features\n\t\t\t}\n\t\t\treturn readInitialState(this.effectiveAppId, 'features_roadmap_features', [])\n\t\t},\n\n\t\t/**\n\t\t * Effective disabled flag. Manifest config > initialState > false.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tresolvedDisabled() {\n\t\t\tif (this.disabled !== null) {\n\t\t\t\treturn this.disabled\n\t\t\t}\n\t\t\treturn readInitialState(this.effectiveAppId, 'features_roadmap_disabled', false)\n\t\t},\n\n\t\tresolvedOpenbuiltUrl() {\n\t\t\treturn this.openbuiltUrl\n\t\t\t\t|| readInitialState(this.effectiveAppId, 'features_roadmap_openbuilt_url', '')\n\t\t},\n\n\t\tresolvedLlmSkillsUrl() {\n\t\t\treturn this.llmSkillsUrl\n\t\t\t\t|| readInitialState(this.effectiveAppId, 'features_roadmap_llm_skills_url', '')\n\t\t},\n\n\t\tresolvedSuggestUrl() {\n\t\t\treturn this.suggestUrl\n\t\t\t\t|| readInitialState(this.effectiveAppId, 'features_roadmap_suggest_url', '')\n\t\t},\n\n\t\tresolvedDocumentationUrl() {\n\t\t\treturn this.documentationUrl\n\t\t\t\t|| readInitialState(this.effectiveAppId, 'features_roadmap_documentation_url', '')\n\t\t},\n\n\t\t/**\n\t\t * Effective capability comparison. Manifest config > initialState >\n\t\t * null.\n\t\t *\n\t\t * `null` and not `{}`: an empty object would make the view believe a\n\t\t * comparison exists and grow a third toggle stop over zero rows.\n\t\t *\n\t\t * @return {object|null}\n\t\t */\n\t\tresolvedCapabilityComparison() {\n\t\t\tif (this.capabilityComparison !== null) {\n\t\t\t\treturn this.capabilityComparison\n\t\t\t}\n\t\t\treturn readInitialState(this.effectiveAppId, 'features_roadmap_capabilities', null)\n\t\t},\n\t},\n\n\tmounted() {\n\t\tif (this.resolvedCapabilityComparison) {\n\t\t\twarnCapabilityComparisonDeprecated('CnFeaturesAndRoadmapPage `capabilityComparison` / the `features_roadmap_capabilities` initial state')\n\t\t}\n\t},\n}\n</script>\n"],"names":["CnFeaturesAndRoadmapView"],"mappings":"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"}
@@ -12,6 +12,7 @@ import __sfc_main$4 from '../CnFeaturesAndRoadmapSidebar/CnFeaturesAndRoadmapSid
12
12
  import __sfc_main$1 from '../CnFeaturesTab/CnFeaturesTab.vue.js';
13
13
  import __sfc_main$2 from '../CnRoadmapTab/CnRoadmapTab.vue.js';
14
14
  import __sfc_main$3 from '../CnSupportDialog/CnSupportDialog.vue.js';
15
+ import { warnCapabilityComparisonDeprecated } from '../../utils/capabilityComparison.js';
15
16
  import { DEFAULT_FORGE, buildFeatureRequestUrl } from '../../utils/forge.js';
16
17
 
17
18
  const DEFAULT_OPENBUILT_PATH = '/apps/openbuilt';
@@ -112,6 +113,11 @@ var script = {
112
113
  * When null (default) the toggle keeps its two stops and the view
113
114
  * renders exactly as it does for every app that passes nothing.
114
115
  *
116
+ * Deprecated: publish the comparison on the app's docs site and link to it (as dossiq #3312 and pipelinq #2195 did). Still rendered; removal waits for a major version.
117
+ * A development build warns once when it is set.
118
+ *
119
+ * @deprecated since 2.66.0, publish the comparison on the app's docs site and link to it.
120
+ * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-the-in-app-capability-comparison-is-deprecated
115
121
  * @type {object|null}
116
122
  */
117
123
  capabilityComparison: {
@@ -425,6 +431,9 @@ var script = {
425
431
 
426
432
  mounted() {
427
433
  this.publishHoistedSidebar();
434
+ if (this.capabilityComparison) {
435
+ warnCapabilityComparisonDeprecated('CnFeaturesAndRoadmapView `capabilityComparison`');
436
+ }
428
437
  },
429
438
 
430
439
  beforeUnmount() {
@@ -1 +1 @@
1
- {"version":3,"file":"CnFeaturesAndRoadmapView.vue2.js","sources":["../../../../src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue"],"sourcesContent":["<template>\n\t<div class=\"cn-features-and-roadmap-view\">\n\t\t<div v-if=\"disabled\" class=\"cn-features-and-roadmap-view__disabled\">\n\t\t\t<NcEmptyContent :name=\"disabledTitle\" :description=\"disabledDescription\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<LockOutline :size=\"48\" />\n\t\t\t\t</template>\n\t\t\t</NcEmptyContent>\n\t\t</div>\n\t\t<template v-else>\n\t\t\t<header class=\"cn-features-and-roadmap-view__header\">\n\t\t\t\t<h2 class=\"cn-features-and-roadmap-view__title\">\n\t\t\t\t\t{{ headerTitle }}\n\t\t\t\t</h2>\n\t\t\t\t<div class=\"cn-features-and-roadmap-view__actions\">\n\t\t\t\t\t<NcButton @click=\"toggleView\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<TableSearch v-if=\"nextView === 'capabilities'\" :size=\"20\" />\n\t\t\t\t\t\t\t<RoadVariant v-else-if=\"nextView === 'roadmap'\" :size=\"20\" />\n\t\t\t\t\t\t\t<FormatListBulleted v-else :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ toggleLabel }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t\t<!-- Direct link to the forge's feature-request issue form\n\t\t\t\t\t (team decision 2026-09-04: the in-product suggestion\n\t\t\t\t\t modal is gone — the forge is where the conversation\n\t\t\t\t\t happens, in English). -->\n\t\t\t\t\t<NcButton\n\t\t\t\t\t\tv-if=\"resolvedSuggestUrl\"\n\t\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\t\t:href=\"resolvedSuggestUrl\"\n\t\t\t\t\t\ttarget=\"_blank\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<Plus :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ suggestLabel }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t</div>\n\t\t\t</header>\n\n\t\t\t<NcNoteCard\n\t\t\t\tv-if=\"resolvedDocumentationUrl\"\n\t\t\t\ttype=\"info\"\n\t\t\t\tclass=\"cn-features-and-roadmap-view__docs-note\">\n\t\t\t\t{{ docsNoteLeading }}\n\t\t\t\t<a\n\t\t\t\t\t:href=\"resolvedDocumentationUrl\"\n\t\t\t\t\t:target=\"documentationUrlIsExternal ? '_blank' : null\"\n\t\t\t\t\t:rel=\"documentationUrlIsExternal ? 'noopener noreferrer' : null\">{{ docsNoteLinkLabel }}</a>\n\t\t\t\t{{ docsNoteTrailing }}\n\t\t\t</NcNoteCard>\n\n\t\t\t<main class=\"cn-features-and-roadmap-view__panel\">\n\t\t\t\t<CnFeaturesTab v-if=\"activeView === 'features'\" :features=\"features\" />\n\t\t\t\t<CnCapabilityTable\n\t\t\t\t\tv-else-if=\"activeView === 'capabilities'\"\n\t\t\t\t\t:comparison=\"capabilityComparison\" />\n\t\t\t\t<CnRoadmapTab v-else :repo=\"repo\" />\n\t\t\t</main>\n\n\t\t\t<CnSupportDialog\n\t\t\t\tv-if=\"showSupportDialog\"\n\t\t\t\t:appName=\"resolvedAppName\"\n\t\t\t\t:appSlug=\"resolvedAppSlug\"\n\t\t\t\t:appStoreUrl=\"resolvedAppStoreUrl\"\n\t\t\t\t:featureRequestUrl=\"resolvedFeatureRequestUrl\"\n\t\t\t\t:donateUrl=\"donateUrl\"\n\t\t\t\t:supportUrl=\"supportUrl\"\n\t\t\t\t:founderName=\"founderName\"\n\t\t\t\t:founderTitle=\"founderTitle\"\n\t\t\t\t@close=\"showSupportDialog = false\" />\n\t\t</template>\n\t</div>\n</template>\n\n<script>\n/**\n * SPDX-License-Identifier: EUPL-1.2\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n *\n * CnFeaturesAndRoadmapView — route-level container for the Features &\n * Roadmap surface. Header carries the view-toggle and the primary\n * Suggest-feature CTA. Body is a card grid (Features OR Roadmap).\n *\n * The right-edge sidebar is hoisted to NcContent level via the same\n * `cnIndexSidebarConfig` provide mechanism CnIndexPage uses for its\n * CnIndexSidebar — that's the only place where Nextcloud's right-edge\n * sidebar slot renders correctly (next to NcAppContent, not inside it).\n * When mounted under CnAppRoot the view publishes\n * `CnFeaturesAndRoadmapSidebar` + props into the holder on mounted()\n * and clears it on beforeUnmount(). Sidebar carries four pitch\n * sections: Suggest, OpenBuilt, LLM, Support. Suggest renders as an\n * anchor to the forge's feature-request issue form (`resolvedSuggestUrl`\n * is always passed); Support emits `@support` (forwarded to a\n * freshly-mounted `CnSupportDialog`).\n *\n * Sidebar link targets are overridable via the `openbuiltUrl` and\n * `llmSkillsUrl` props. Defaults: in-instance `/apps/openbuilt` (via\n * `generateUrl`) and `https://docs.conduction.nl/ai-skills`.\n *\n * Spec: features-roadmap-component — Requirement \"CnFeaturesAndRoadmapView\".\n */\nimport { translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport { NcButton, NcEmptyContent, NcNoteCard } from '@nextcloud/vue'\nimport { markRaw } from 'vue'\nimport FormatListBulleted from 'vue-material-design-icons/FormatListBulleted.vue'\nimport LockOutline from 'vue-material-design-icons/LockOutline.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport RoadVariant from 'vue-material-design-icons/RoadVariant.vue'\nimport TableSearch from 'vue-material-design-icons/TableSearch.vue'\nimport CnCapabilityTable from '../CnCapabilityTable/CnCapabilityTable.vue'\nimport CnFeaturesAndRoadmapSidebar from '../CnFeaturesAndRoadmapSidebar/CnFeaturesAndRoadmapSidebar.vue'\nimport CnFeaturesTab from '../CnFeaturesTab/CnFeaturesTab.vue'\nimport CnRoadmapTab from '../CnRoadmapTab/CnRoadmapTab.vue'\nimport CnSupportDialog from '../CnSupportDialog/CnSupportDialog.vue'\nimport { buildFeatureRequestUrl, DEFAULT_FORGE } from '../../utils/forge.js'\n\nconst DEFAULT_OPENBUILT_PATH = '/apps/openbuilt'\nconst DEFAULT_LLM_SKILLS_URL = 'https://docs.conduction.nl/ai-skills'\nconst DEFAULT_DONATE_URL = 'https://github.com/sponsors/ConductionNL'\nconst DEFAULT_SUPPORT_URL = 'https://www.conduction.nl/support'\nconst DEFAULT_FOUNDER_NAME = 'Ruben van der Linde'\nconst DEFAULT_FOUNDER_TITLE = 'a founder of Conduction'\n\nexport default {\n\tname: 'CnFeaturesAndRoadmapView',\n\n\tcomponents: {\n\t\tNcButton,\n\t\tNcEmptyContent,\n\t\tNcNoteCard,\n\t\tFormatListBulleted,\n\t\tLockOutline,\n\t\tPlus,\n\t\tRoadVariant,\n\t\tTableSearch,\n\t\tCnCapabilityTable,\n\t\tCnFeaturesTab,\n\t\tCnRoadmapTab,\n\t\tCnSupportDialog,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * Set to `true` by CnAppRoot so descendants know the host will\n\t\t * mount a hoisted sidebar at NcContent level (see CnAppRoot.vue\n\t\t * `cnHostsIndexSidebar: true`). When `false` (no CnAppRoot\n\t\t * ancestor) the view skips publishing — there's no inline\n\t\t * fallback for this surface.\n\t\t */\n\t\tcnHostsIndexSidebar: { default: false },\n\t\t/**\n\t\t * Reactive holder published by CnAppRoot that backs the\n\t\t * hoisted-sidebar render in NcContent. Set value to\n\t\t * `{component, props, listeners}` to mount; set to `null` to\n\t\t * unmount. Same holder CnIndexPage uses.\n\t\t */\n\t\tcnIndexSidebarConfig: { default: () => ({ value: null }) },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * `<owner>/<repo>` of the app's repository on the forge.\n\t\t */\n\t\trepo: {\n\t\t\ttype: String,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/**\n\t\t * Target forge the feature-request deep-links are built against\n\t\t * (the Suggest CTA and the support dialog's feature-request URL).\n\t\t * Set `{type: 'github'}` (optionally with `baseUrl`) to switch\n\t\t * forge.\n\t\t *\n\t\t * @type {{type: 'codeberg'|'forgejo'|'gitea'|'github', baseUrl?: string}}\n\t\t */\n\t\tforge: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({ ...DEFAULT_FORGE }),\n\t\t},\n\n\t\t/**\n\t\t * Build-time feature manifest (alphabetical list rendered by CnFeaturesTab).\n\t\t *\n\t\t * @type {Array<{slug: string, title: string, summary: string, docsUrl: string}>}\n\t\t */\n\t\tfeatures: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * Admin opt-out flag — when true the entire view collapses to a\n\t\t * single \"disabled by admin\" empty state.\n\t\t */\n\t\tdisabled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Optional capability comparison. When supplied, the header toggle\n\t\t * gains a third stop that renders `CnCapabilityTable`: a searchable\n\t\t * table of the rows, grouped by feature or by area, with a\n\t\t * provided-by column naming which app or platform delivers each one.\n\t\t *\n\t\t * Shape: `{systems, areas, features?, providers?, capabilities}`. The\n\t\t * rows carry the competitor ratings they already carry, plus the\n\t\t * optional `provider`, `providerHow`, `feature` and\n\t\t * `featureConfidence` fields.\n\t\t *\n\t\t * When null (default) the toggle keeps its two stops and the view\n\t\t * renders exactly as it does for every app that passes nothing.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tcapabilityComparison: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Override the OpenBuilt sidebar CTA target. Pass an absolute URL or\n\t\t * a Nextcloud-relative path. When unset, defaults to the in-instance\n\t\t * OpenBuilt route at `/apps/openbuilt` (resolved via `generateUrl`).\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\topenbuiltUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Override the LLM-skills sidebar CTA target. Defaults to\n\t\t * `https://docs.conduction.nl/ai-skills`.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tllmSkillsUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Optional override for the Suggest CTA inside the sidebar. When\n\t\t * set the CTA renders as an anchor pointing at this URL —\n\t\t * appropriate when the app routes feature suggestions through a\n\t\t * public form, a Discord channel, or any non-forge target. When\n\t\t * empty (default) the CTA links the forge's feature-request\n\t\t * issue form derived from `repo` + `forge`.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tsuggestUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Optional URL of the app's public documentation site. When set,\n\t\t * an info banner is rendered above the card grid pointing users at\n\t\t * `<docs>` for full technical + user docs. Per-app — pipelinq\n\t\t * passes `https://pipelinq.conduction.nl`, decidesk passes its\n\t\t * own, etc. When empty (default) no banner renders.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tdocumentationUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Display name of the host app, used as the title of the bundled\n\t\t * `CnSupportDialog` and interpolated into its body copy. When\n\t\t * empty (default) the View derives a humanised label from `repo`\n\t\t * — `ConductionNL/openregister` becomes `Openregister` — which is\n\t\t * fine for stock apps and overridable for camelCased names.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tappName: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Kebab-case host-app id passed to `CnSupportDialog` (its\n\t\t * localStorage namespace). When empty (default) the View derives\n\t\t * it from `repo` — second segment lower-cased.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tappSlug: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Nextcloud App Store listing URL for the host app, threaded\n\t\t * through to `CnSupportDialog`. When empty (default) the View\n\t\t * derives `https://apps.nextcloud.com/apps/{appSlug}`.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tappStoreUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * URL the \"Suggest a feature\" CTA inside `CnSupportDialog` opens.\n\t\t * When empty (default) the View derives\n\t\t * `<forge-base>/{repo}/issues/new` from the `forge` prop. This is\n\t\t * intentionally separate from the prop-of-the-same-name on the\n\t\t * sidebar's Suggest CTA — that one opens the modal-driven flow;\n\t\t * this one is the \"open on the forge\" fallback used by the support\n\t\t * dialog, which is meant for the casual visitor rather than the\n\t\t * existing suggest-feature loop.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tfeatureRequestUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Donate-CTA URL passed to `CnSupportDialog`. Defaults to\n\t\t * ConductionNL's GitHub Sponsors page.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tdonateUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: DEFAULT_DONATE_URL,\n\t\t},\n\n\t\t/**\n\t\t * Business-support CTA URL passed to `CnSupportDialog`. Defaults\n\t\t * to the Conduction contact page.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tsupportUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: DEFAULT_SUPPORT_URL,\n\t\t},\n\n\t\t/**\n\t\t * Name rendered in the handwritten signature of `CnSupportDialog`.\n\t\t * Defaults to Ruben van der Linde.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tfounderName: {\n\t\t\ttype: String,\n\t\t\tdefault: DEFAULT_FOUNDER_NAME,\n\t\t},\n\n\t\t/**\n\t\t * Title shown after the handwritten signature in `CnSupportDialog`\n\t\t * (e.g. \"Founder\", \"Oprichter\").\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tfounderTitle: {\n\t\t\ttype: String,\n\t\t\tdefault: DEFAULT_FOUNDER_TITLE,\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tactiveView: 'features',\n\t\t\tshowSupportDialog: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The stops the header toggle cycles through. Without a capability\n\t\t * comparison this is the two-stop list every consumer has today, and\n\t\t * the toggle behaves exactly as it did.\n\t\t *\n\t\t * @return {Array<string>} The views, in cycle order.\n\t\t */\n\t\tavailableViews() {\n\t\t\treturn this.capabilityComparison\n\t\t\t\t? ['features', 'capabilities', 'roadmap']\n\t\t\t\t: ['features', 'roadmap']\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The view the toggle moves to next.\n\t\t */\n\t\tnextView() {\n\t\t\tconst views = this.availableViews\n\t\t\tconst index = views.indexOf(this.activeView)\n\t\t\treturn views[(index + 1) % views.length]\n\t\t},\n\n\t\theaderTitle() {\n\t\t\treturn this.viewTitle(this.activeView)\n\t\t},\n\n\t\ttoggleLabel() {\n\t\t\tif (this.nextView === 'capabilities') {\n\t\t\t\treturn t('nextcloud-vue', 'Show capabilities')\n\t\t\t}\n\t\t\treturn this.nextView === 'roadmap'\n\t\t\t\t? t('nextcloud-vue', 'Show roadmap')\n\t\t\t\t: t('nextcloud-vue', 'Show features')\n\t\t},\n\n\t\tsuggestLabel() {\n\t\t\treturn t('nextcloud-vue', 'Suggest feature')\n\t\t},\n\n\t\tdisabledTitle() {\n\t\t\treturn t('nextcloud-vue', 'This feature has been disabled by your administrator')\n\t\t},\n\n\t\tdisabledDescription() {\n\t\t\treturn t('nextcloud-vue', 'Contact your Nextcloud administrator to enable Features & Roadmap on this instance.')\n\t\t},\n\n\t\tresolvedOpenbuiltUrl() {\n\t\t\tif (this.openbuiltUrl) {\n\t\t\t\treturn this.openbuiltUrl\n\t\t\t}\n\t\t\treturn generateUrl(DEFAULT_OPENBUILT_PATH)\n\t\t},\n\n\t\tresolvedLlmSkillsUrl() {\n\t\t\treturn this.llmSkillsUrl || DEFAULT_LLM_SKILLS_URL\n\t\t},\n\n\t\tresolvedDocumentationUrl() {\n\t\t\treturn this.documentationUrl || ''\n\t\t},\n\n\t\trepoSlug() {\n\t\t\tif (!this.repo || !this.repo.includes('/')) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn this.repo.split('/')[1].toLowerCase()\n\t\t},\n\n\t\tresolvedAppSlug() {\n\t\t\treturn this.appSlug || this.repoSlug\n\t\t},\n\n\t\tresolvedAppName() {\n\t\t\tif (this.appName) {\n\t\t\t\treturn this.appName\n\t\t\t}\n\t\t\tconst slug = this.repoSlug\n\t\t\tif (!slug) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn slug.charAt(0).toUpperCase() + slug.slice(1)\n\t\t},\n\n\t\tresolvedAppStoreUrl() {\n\t\t\treturn this.appStoreUrl || (this.resolvedAppSlug\n\t\t\t\t? `https://apps.nextcloud.com/apps/${this.resolvedAppSlug}`\n\t\t\t\t: '')\n\t\t},\n\n\t\tresolvedFeatureRequestUrl() {\n\t\t\tif (this.featureRequestUrl) {\n\t\t\t\treturn this.featureRequestUrl\n\t\t\t}\n\t\t\tif (!this.repo) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\t// The feature-request issue FORM, not the blank new-issue page —\n\t\t\t// the form's structured fields replaced the removed in-product\n\t\t\t// suggestion modal.\n\t\t\treturn buildFeatureRequestUrl(this.forge, this.repo)\n\t\t},\n\n\t\t/**\n\t\t * The Suggest CTA's target: the host's `suggestUrl` override when\n\t\t * given (a public form, a Discord channel), else the forge's\n\t\t * feature-request issue form.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tresolvedSuggestUrl() {\n\t\t\treturn this.suggestUrl || this.resolvedFeatureRequestUrl\n\t\t},\n\n\t\tdocumentationUrlIsExternal() {\n\t\t\treturn /^https?:\\/\\//i.test(this.resolvedDocumentationUrl)\n\t\t},\n\n\t\tdocsNoteLeading() {\n\t\t\treturn t('nextcloud-vue', 'Looking for documentation? Visit')\n\t\t},\n\n\t\tdocsNoteLinkLabel() {\n\t\t\t// Strip protocol for a cleaner inline link label.\n\t\t\treturn this.resolvedDocumentationUrl.replace(/^https?:\\/\\//i, '')\n\t\t},\n\n\t\tdocsNoteTrailing() {\n\t\t\treturn t('nextcloud-vue', 'for all technical and user documentation.')\n\t\t},\n\t},\n\n\twatch: {\n\t\t// A comparison that goes away leaves the reader on a stop that no\n\t\t// longer exists: the panel's v-else-if still matches, so it renders an\n\t\t// empty CnCapabilityTable under a \"Capabilities\" heading until the\n\t\t// reader thinks to press the toggle.\n\t\tavailableViews(views) {\n\t\t\tif (!views.includes(this.activeView)) {\n\t\t\t\tthis.activeView = views[0]\n\t\t\t}\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.publishHoistedSidebar()\n\t},\n\n\tbeforeUnmount() {\n\t\t// Clear the holder so the hoisted sidebar disappears when the\n\t\t// user navigates away from this route. Mirrors CnIndexPage.\n\t\tif (this.cnHostsIndexSidebar && this.cnIndexSidebarConfig) {\n\t\t\tthis.cnIndexSidebarConfig.value = null\n\t\t}\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * @param {string} view One of the `availableViews`.\n\t\t * @return {string} The heading for that view.\n\t\t */\n\t\tviewTitle(view) {\n\t\t\tif (view === 'capabilities') {\n\t\t\t\treturn t('nextcloud-vue', 'Capabilities')\n\t\t\t}\n\t\t\treturn view === 'roadmap'\n\t\t\t\t? t('nextcloud-vue', 'Roadmap')\n\t\t\t\t: t('nextcloud-vue', 'Features')\n\t\t},\n\n\t\ttoggleView() {\n\t\t\tthis.activeView = this.nextView\n\t\t},\n\n\t\topenSupportDialog() {\n\t\t\tthis.showSupportDialog = true\n\t\t},\n\n\t\t/**\n\t\t * Publish the hoisted-sidebar config to `cnIndexSidebarConfig`\n\t\t * (same holder CnIndexPage uses) so CnAppRoot mounts the\n\t\t * CnFeaturesAndRoadmapSidebar at NcContent level. The sidebar\n\t\t * emits `suggest` when the user clicks the Suggest CTA inside\n\t\t * it; the view forwards that to `openSuggestModal()`. No-op\n\t\t * when there's no CnAppRoot ancestor.\n\t\t */\n\t\tpublishHoistedSidebar() {\n\t\t\tif (!this.cnHostsIndexSidebar || !this.cnIndexSidebarConfig) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.disabled) {\n\t\t\t\tthis.cnIndexSidebarConfig.value = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.cnIndexSidebarConfig.value = {\n\t\t\t\t// markRaw: see CnIndexPage's publishHoistedSidebar — the holder\n\t\t\t\t// is deeply reactive and would proxy the component definition.\n\t\t\t\tcomponent: markRaw(CnFeaturesAndRoadmapSidebar),\n\t\t\t\tprops: {\n\t\t\t\t\topenbuiltUrl: this.resolvedOpenbuiltUrl,\n\t\t\t\t\tllmSkillsUrl: this.resolvedLlmSkillsUrl,\n\t\t\t\t\t// Always a URL now (the removed in-product modal was the\n\t\t\t\t\t// only non-link path), so the sidebar CTA always renders\n\t\t\t\t\t// as an anchor and its `suggest` emit path stays idle.\n\t\t\t\t\tsuggestUrl: this.resolvedSuggestUrl,\n\t\t\t\t},\n\n\t\t\t\tlisteners: {\n\t\t\t\t\tsupport: () => this.openSupportDialog(),\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-features-and-roadmap-view {\n\tmax-width: 1200px;\n\tmargin: 0 auto;\n\tpadding: 24px 16px;\n}\n\n.cn-features-and-roadmap-view__header {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 12px;\n\tmargin-bottom: 24px;\n\tflex-wrap: wrap;\n\t/* Clear the Nextcloud navigation toggle button (44px wide, absolutely\n\t positioned at the left edge of .app-content) plus 12px breathing\n\t room. Only the HEADER shifts right — the panel below keeps full width. */\n\tpadding-inline-start: 56px;\n}\n\n.cn-features-and-roadmap-view__title {\n\tfont-size: 1.4em;\n\tmargin: 0;\n\tcolor: var(--color-main-text);\n}\n\n.cn-features-and-roadmap-view__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\talign-items: center;\n\tflex-wrap: wrap;\n}\n\n.cn-features-and-roadmap-view__panel {\n\tmin-height: 320px;\n}\n\n/* Underline the inline docs URL so it reads as a link inside the note\n body — NcNoteCard's default link styling drops the underline by\n inheriting from the host theme. */\n.cn-features-and-roadmap-view__docs-note :deep(a) {\n\tcolor: var(--color-primary-element);\n\ttext-decoration: underline;\n}\n\n.cn-features-and-roadmap-view__docs-note :deep(a:hover) {\n\ttext-decoration: underline;\n\topacity: 0.85;\n}\n</style>\n"],"names":["CnCapabilityTable","CnFeaturesTab","CnRoadmapTab","CnSupportDialog","t","CnFeaturesAndRoadmapSidebar"],"mappings":";;;;;;;;;;;;;;;;AAsHA,MAAM,sBAAqB,GAAI,kBAAgB;AAC/C,MAAM,sBAAqB,GAAI,uCAAqC;AACpE,MAAM,kBAAiB,GAAI,2CAAyC;AACpE,MAAM,mBAAoB,GAAE,oCAAkC;AAC9D,MAAM,oBAAqB,GAAE,sBAAoB;AACjD,MAAM,qBAAsB,GAAE,0BAAwB;;AAEtD,aAAe;AACd,CAAA,IAAI,EAAE,0BAA0B;;CAEhC,UAAU,EAAE;AACX,EAAA,QAAQ;AACR,EAAA,cAAc;EACd,UAAU;AACV,EAAA,kBAAkB;EAClB,WAAW;EACX,IAAI;EACJ,WAAW;EACX,WAAW;AACX,qBAAAA,UAAiB;iBACjBC,YAAa;gBACbC,YAAY;AACZ,mBAAAC,YAAe;EACf;;AAED,CAAA,MAAM,EAAE;AACP;;;;;;;AAOA,EAAA,mBAAmB,EAAE,EAAE,OAAO,EAAE,OAAO;AACvC;;AAEC;;AAEA;;EAED,oBAAoB,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,IAAG,EAAG,CAAA,EAAG;EAC1D;;AAED,CAAA,KAAK,EAAE;AACN;;;EAGA,IAAI,EAAE;GACL,IAAI,EAAE,MAAM;GACZ,QAAQ,EAAE,IAAI;GACd;;AAED;;;;AAIC;AACA;AACA;;AAED,EAAA,KAAK,EAAE;GACN,IAAI,EAAE,MAAM;AACZ,GAAA,OAAO,EAAE,OAAO,EAAE,GAAG,aAAY,EAAG,CAAC;GACrC;;AAED;AACC;AACA;AACA;;AAED,EAAA,QAAQ,EAAE;GACT,IAAI,EAAE,KAAK;AACX,GAAA,OAAO,EAAE,MAAM,EAAE;GACjB;;AAED;;AAEC;;AAED,EAAA,QAAQ,EAAE;GACT,IAAI,EAAE,OAAO;GACb,OAAO,EAAE,KAAK;GACd;;AAED;;;AAGC;;AAEA;;;AAGA;;AAEA;;;AAGA;AACA;;AAED,EAAA,oBAAoB,EAAE;GACrB,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,IAAI;GACb;;AAED;;;;AAIC;;;EAGD,YAAY,EAAE;GACb,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;AAEC;AACA;;;EAGD,YAAY,EAAE;GACb,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;AAEC;;;;AAIA;AACA;;;EAGD,UAAU,EAAE;GACX,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;;;;AAKC;AACA;;;AAGD,EAAA,gBAAgB,EAAE;GACjB,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;;;;;AAMC;;;AAGD,EAAA,OAAO,EAAE;GACR,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;;AAGC;AACA;;;AAGD,EAAA,OAAO,EAAE;GACR,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;;;AAIC;;;EAGD,WAAW,EAAE;GACZ,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;AAEC;;;;;;;AAOA;;;AAGD,EAAA,iBAAiB,EAAE;GAClB,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;AAEC;AACA;;;EAGD,SAAS,EAAE;GACV,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,kBAAkB;GAC3B;;AAED;;;AAGC;;;EAGD,UAAU,EAAE;GACX,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,mBAAmB;GAC5B;;AAED;;;AAGC;;;EAGD,WAAW,EAAE;GACZ,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,oBAAoB;GAC7B;;AAED;;;AAGC;;;EAGD,YAAY,EAAE;GACb,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,qBAAqB;GAC9B;EACD;;AAED,CAAA,IAAI,GAAG;AACN,EAAA,OAAO;AACN,GAAA,UAAU,EAAE,UAAU;GACtB,iBAAiB,EAAE,KAAK;AACzB,GAAA;EACA;;AAED,CAAA,QAAQ,EAAE;AACT;;;AAGC;AACA;AACA;;AAED,EAAA,cAAc,GAAG;AAChB,GAAA,OAAO,IAAI,CAAC,oBAAmB;AAC9B,MAAE,CAAC,UAAU,EAAE,cAAc,EAAE,SAAS,CAAA;MACtC,CAAC,UAAU,EAAE,SAAS,CAAA;GACzB;;AAED;AACC;;EAED,QAAQ,GAAG;GACV,MAAM,KAAI,GAAI,IAAI,CAAC,eAAa;AAChC,GAAA,MAAM,KAAM,GAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,EAAA;AAC3C,GAAA,OAAO,KAAK,CAAC,CAAC,KAAM,GAAE,CAAC,IAAI,KAAK,CAAC,MAAM,CAAA;GACvC;;EAED,WAAW,GAAG;AACb,GAAA,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,UAAU,CAAA;GACrC;;EAED,WAAW,GAAG;AACb,GAAA,IAAI,IAAI,CAAC,QAAS,KAAI,cAAc,EAAE;AACrC,IAAA,OAAOC,SAAC,CAAC,eAAe,EAAE,mBAAmB,CAAA;AAC9C,IAAA;AACA,GAAA,OAAO,IAAI,CAAC,QAAO,KAAM,SAAQ;AAChC,MAAEA,SAAC,CAAC,eAAe,EAAE,cAAc,CAAA;AACnC,MAAEA,SAAC,CAAC,eAAe,EAAE,eAAe,CAAA;GACrC;;AAED,EAAA,YAAY,GAAG;AACd,GAAA,OAAOA,SAAC,CAAC,eAAe,EAAE,iBAAiB,CAAA;GAC3C;;AAED,EAAA,aAAa,GAAG;AACf,GAAA,OAAOA,SAAC,CAAC,eAAe,EAAE,sDAAsD,CAAA;GAChF;;AAED,EAAA,mBAAmB,GAAG;GACrB,OAAOA,SAAC,CAAC,eAAe,EAAE,qFAAqF,CAAA;GAC/G;;EAED,oBAAoB,GAAG;AACtB,GAAA,IAAI,IAAI,CAAC,YAAY,EAAE;IACtB,OAAO,IAAI,CAAC,YAAW;AACxB,IAAA;AACA,GAAA,OAAO,WAAW,CAAC,sBAAsB,CAAA;GACzC;;EAED,oBAAoB,GAAG;GACtB,OAAO,IAAI,CAAC,YAAW,IAAK,sBAAqB;GACjD;;EAED,wBAAwB,GAAG;AAC1B,GAAA,OAAO,IAAI,CAAC,gBAAe,IAAK,EAAC;GACjC;;EAED,QAAQ,GAAG;AACV,GAAA,IAAI,CAAC,IAAI,CAAC,IAAK,IAAG,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE;AAC3C,IAAA,OAAO,EAAC;AACT,IAAA;AACA,GAAA,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAC;GAC3C;;AAED,EAAA,eAAe,GAAG;AACjB,GAAA,OAAO,IAAI,CAAC,OAAM,IAAK,IAAI,CAAC,QAAO;GACnC;;AAED,EAAA,eAAe,GAAG;AACjB,GAAA,IAAI,IAAI,CAAC,OAAO,EAAE;AACjB,IAAA,OAAO,IAAI,CAAC,OAAM;AACnB,IAAA;GACA,MAAM,OAAO,IAAI,CAAC,SAAO;AACzB,GAAA,IAAI,CAAC,IAAI,EAAE;AACV,IAAA,OAAO,EAAC;AACT,IAAA;GACA,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAG,GAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAA;GAClD;;AAED,EAAA,mBAAmB,GAAG;AACrB,GAAA,OAAO,IAAI,CAAC,WAAU,KAAM,IAAI,CAAC,eAAc;MAC5C,CAAC,gCAAgC,EAAE,IAAI,CAAC,eAAe,CAAC,CAAA;AAC1D,MAAE,EAAE,CAAA;GACL;;EAED,yBAAyB,GAAG;GAC3B,IAAI,IAAI,CAAC,iBAAiB,EAAE;IAC3B,OAAO,IAAI,CAAC,iBAAgB;AAC7B,IAAA;AACA,GAAA,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;AACf,IAAA,OAAO,EAAC;AACT,IAAA;;;AAGA;GACA,OAAO,sBAAsB,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,CAAA;GACnD;;AAED;;;;AAIC;AACA;;AAED,EAAA,kBAAkB,GAAG;GACpB,OAAO,IAAI,CAAC,UAAW,IAAG,IAAI,CAAC,yBAAwB;GACvD;;EAED,0BAA0B,GAAG;GAC5B,OAAO,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,wBAAwB,CAAA;GACzD;;AAED,EAAA,eAAe,GAAG;GACjB,OAAOA,SAAC,CAAC,eAAe,EAAE,kCAAkC,CAAA;GAC5D;;AAED,EAAA,iBAAiB,GAAG;AACnB;AACA,GAAA,OAAO,IAAI,CAAC,wBAAwB,CAAC,OAAO,CAAC,eAAe,EAAE,EAAE,CAAA;GAChE;;AAED,EAAA,gBAAgB,GAAG;AAClB,GAAA,OAAOA,SAAC,CAAC,eAAe,EAAE,2CAA2C,CAAA;GACrE;EACD;;AAED,CAAA,KAAK,EAAE;;;;AAIN;AACA,EAAA,cAAc,CAAC,KAAK,EAAE;AACrB,GAAA,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE;IACrC,IAAI,CAAC,aAAa,KAAK,CAAC,CAAC,EAAA;AAC1B,IAAA;GACA;EACD;;AAED,CAAA,OAAO,GAAG;EACT,IAAI,CAAC,qBAAqB,GAAC;EAC3B;;AAED,CAAA,aAAa,GAAG;;;EAGf,IAAI,IAAI,CAAC,mBAAkB,IAAK,IAAI,CAAC,oBAAoB,EAAE;AAC1D,GAAA,IAAI,CAAC,oBAAoB,CAAC,KAAI,GAAI,KAAG;AACtC,GAAA;EACA;;AAED,CAAA,OAAO,EAAE;AACR;AACC;AACA;;AAED,EAAA,SAAS,CAAC,IAAI,EAAE;GACf,IAAI,IAAK,KAAI,cAAc,EAAE;AAC5B,IAAA,OAAOA,SAAC,CAAC,eAAe,EAAE,cAAc,CAAA;AACzC,IAAA;AACA,GAAA,OAAO,IAAG,KAAM,SAAQ;MACrBA,SAAC,CAAC,eAAe,EAAE,SAAS,CAAA;MAC5BA,SAAC,CAAC,eAAe,EAAE,UAAU,CAAA;GAChC;;EAED,UAAU,GAAG;GACZ,IAAI,CAAC,aAAa,IAAI,CAAC,SAAO;GAC9B;;AAED,EAAA,iBAAiB,GAAG;GACnB,IAAI,CAAC,iBAAkB,GAAE,KAAG;GAC5B;;AAED;;;;;;AAMC;;EAED,qBAAqB,GAAG;GACvB,IAAI,CAAC,IAAI,CAAC,mBAAkB,IAAK,CAAC,IAAI,CAAC,oBAAoB,EAAE;IAC5D,MAAK;AACN,IAAA;AACA,GAAA,IAAI,IAAI,CAAC,QAAQ,EAAE;AAClB,IAAA,IAAI,CAAC,oBAAoB,CAAC,KAAI,GAAI,KAAG;IACrC,MAAK;AACN,IAAA;AACA,GAAA,IAAI,CAAC,oBAAoB,CAAC,QAAQ;;;IAGjC,SAAS,EAAE,OAAO,CAACC,YAA2B,CAAC;AAC/C,IAAA,KAAK,EAAE;AACN,KAAA,YAAY,EAAE,IAAI,CAAC,oBAAoB;AACvC,KAAA,YAAY,EAAE,IAAI,CAAC,oBAAoB;;;AAGvC;AACA,KAAA,UAAU,EAAE,IAAI,CAAC,kBAAkB;KACnC;;IAED,SAAS,EAAE;AACV,KAAA,OAAO,EAAE,MAAM,IAAI,CAAC,iBAAiB,EAAE;KACvC;AACF,KAAA;GACA;EACD;AACF;;;;"}
1
+ {"version":3,"file":"CnFeaturesAndRoadmapView.vue2.js","sources":["../../../../src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue"],"sourcesContent":["<template>\n\t<div class=\"cn-features-and-roadmap-view\">\n\t\t<div v-if=\"disabled\" class=\"cn-features-and-roadmap-view__disabled\">\n\t\t\t<NcEmptyContent :name=\"disabledTitle\" :description=\"disabledDescription\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<LockOutline :size=\"48\" />\n\t\t\t\t</template>\n\t\t\t</NcEmptyContent>\n\t\t</div>\n\t\t<template v-else>\n\t\t\t<header class=\"cn-features-and-roadmap-view__header\">\n\t\t\t\t<h2 class=\"cn-features-and-roadmap-view__title\">\n\t\t\t\t\t{{ headerTitle }}\n\t\t\t\t</h2>\n\t\t\t\t<div class=\"cn-features-and-roadmap-view__actions\">\n\t\t\t\t\t<NcButton @click=\"toggleView\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<TableSearch v-if=\"nextView === 'capabilities'\" :size=\"20\" />\n\t\t\t\t\t\t\t<RoadVariant v-else-if=\"nextView === 'roadmap'\" :size=\"20\" />\n\t\t\t\t\t\t\t<FormatListBulleted v-else :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ toggleLabel }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t\t<!-- Direct link to the forge's feature-request issue form\n\t\t\t\t\t (team decision 2026-09-04: the in-product suggestion\n\t\t\t\t\t modal is gone — the forge is where the conversation\n\t\t\t\t\t happens, in English). -->\n\t\t\t\t\t<NcButton\n\t\t\t\t\t\tv-if=\"resolvedSuggestUrl\"\n\t\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\t\t:href=\"resolvedSuggestUrl\"\n\t\t\t\t\t\ttarget=\"_blank\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<Plus :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ suggestLabel }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t</div>\n\t\t\t</header>\n\n\t\t\t<NcNoteCard\n\t\t\t\tv-if=\"resolvedDocumentationUrl\"\n\t\t\t\ttype=\"info\"\n\t\t\t\tclass=\"cn-features-and-roadmap-view__docs-note\">\n\t\t\t\t{{ docsNoteLeading }}\n\t\t\t\t<a\n\t\t\t\t\t:href=\"resolvedDocumentationUrl\"\n\t\t\t\t\t:target=\"documentationUrlIsExternal ? '_blank' : null\"\n\t\t\t\t\t:rel=\"documentationUrlIsExternal ? 'noopener noreferrer' : null\">{{ docsNoteLinkLabel }}</a>\n\t\t\t\t{{ docsNoteTrailing }}\n\t\t\t</NcNoteCard>\n\n\t\t\t<main class=\"cn-features-and-roadmap-view__panel\">\n\t\t\t\t<CnFeaturesTab v-if=\"activeView === 'features'\" :features=\"features\" />\n\t\t\t\t<CnCapabilityTable\n\t\t\t\t\tv-else-if=\"activeView === 'capabilities'\"\n\t\t\t\t\t:comparison=\"capabilityComparison\" />\n\t\t\t\t<CnRoadmapTab v-else :repo=\"repo\" />\n\t\t\t</main>\n\n\t\t\t<CnSupportDialog\n\t\t\t\tv-if=\"showSupportDialog\"\n\t\t\t\t:appName=\"resolvedAppName\"\n\t\t\t\t:appSlug=\"resolvedAppSlug\"\n\t\t\t\t:appStoreUrl=\"resolvedAppStoreUrl\"\n\t\t\t\t:featureRequestUrl=\"resolvedFeatureRequestUrl\"\n\t\t\t\t:donateUrl=\"donateUrl\"\n\t\t\t\t:supportUrl=\"supportUrl\"\n\t\t\t\t:founderName=\"founderName\"\n\t\t\t\t:founderTitle=\"founderTitle\"\n\t\t\t\t@close=\"showSupportDialog = false\" />\n\t\t</template>\n\t</div>\n</template>\n\n<script>\n/**\n * SPDX-License-Identifier: EUPL-1.2\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n *\n * CnFeaturesAndRoadmapView — route-level container for the Features &\n * Roadmap surface. Header carries the view-toggle and the primary\n * Suggest-feature CTA. Body is a card grid (Features OR Roadmap).\n *\n * The right-edge sidebar is hoisted to NcContent level via the same\n * `cnIndexSidebarConfig` provide mechanism CnIndexPage uses for its\n * CnIndexSidebar — that's the only place where Nextcloud's right-edge\n * sidebar slot renders correctly (next to NcAppContent, not inside it).\n * When mounted under CnAppRoot the view publishes\n * `CnFeaturesAndRoadmapSidebar` + props into the holder on mounted()\n * and clears it on beforeUnmount(). Sidebar carries four pitch\n * sections: Suggest, OpenBuilt, LLM, Support. Suggest renders as an\n * anchor to the forge's feature-request issue form (`resolvedSuggestUrl`\n * is always passed); Support emits `@support` (forwarded to a\n * freshly-mounted `CnSupportDialog`).\n *\n * Sidebar link targets are overridable via the `openbuiltUrl` and\n * `llmSkillsUrl` props. Defaults: in-instance `/apps/openbuilt` (via\n * `generateUrl`) and `https://docs.conduction.nl/ai-skills`.\n *\n * Spec: features-roadmap-component — Requirement \"CnFeaturesAndRoadmapView\".\n */\nimport { translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport { NcButton, NcEmptyContent, NcNoteCard } from '@nextcloud/vue'\nimport { markRaw } from 'vue'\nimport FormatListBulleted from 'vue-material-design-icons/FormatListBulleted.vue'\nimport LockOutline from 'vue-material-design-icons/LockOutline.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport RoadVariant from 'vue-material-design-icons/RoadVariant.vue'\nimport TableSearch from 'vue-material-design-icons/TableSearch.vue'\nimport CnCapabilityTable from '../CnCapabilityTable/CnCapabilityTable.vue'\nimport CnFeaturesAndRoadmapSidebar from '../CnFeaturesAndRoadmapSidebar/CnFeaturesAndRoadmapSidebar.vue'\nimport CnFeaturesTab from '../CnFeaturesTab/CnFeaturesTab.vue'\nimport CnRoadmapTab from '../CnRoadmapTab/CnRoadmapTab.vue'\nimport CnSupportDialog from '../CnSupportDialog/CnSupportDialog.vue'\nimport { warnCapabilityComparisonDeprecated } from '../../utils/capabilityComparison.js'\nimport { buildFeatureRequestUrl, DEFAULT_FORGE } from '../../utils/forge.js'\n\nconst DEFAULT_OPENBUILT_PATH = '/apps/openbuilt'\nconst DEFAULT_LLM_SKILLS_URL = 'https://docs.conduction.nl/ai-skills'\nconst DEFAULT_DONATE_URL = 'https://github.com/sponsors/ConductionNL'\nconst DEFAULT_SUPPORT_URL = 'https://www.conduction.nl/support'\nconst DEFAULT_FOUNDER_NAME = 'Ruben van der Linde'\nconst DEFAULT_FOUNDER_TITLE = 'a founder of Conduction'\n\nexport default {\n\tname: 'CnFeaturesAndRoadmapView',\n\n\tcomponents: {\n\t\tNcButton,\n\t\tNcEmptyContent,\n\t\tNcNoteCard,\n\t\tFormatListBulleted,\n\t\tLockOutline,\n\t\tPlus,\n\t\tRoadVariant,\n\t\tTableSearch,\n\t\tCnCapabilityTable,\n\t\tCnFeaturesTab,\n\t\tCnRoadmapTab,\n\t\tCnSupportDialog,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * Set to `true` by CnAppRoot so descendants know the host will\n\t\t * mount a hoisted sidebar at NcContent level (see CnAppRoot.vue\n\t\t * `cnHostsIndexSidebar: true`). When `false` (no CnAppRoot\n\t\t * ancestor) the view skips publishing — there's no inline\n\t\t * fallback for this surface.\n\t\t */\n\t\tcnHostsIndexSidebar: { default: false },\n\t\t/**\n\t\t * Reactive holder published by CnAppRoot that backs the\n\t\t * hoisted-sidebar render in NcContent. Set value to\n\t\t * `{component, props, listeners}` to mount; set to `null` to\n\t\t * unmount. Same holder CnIndexPage uses.\n\t\t */\n\t\tcnIndexSidebarConfig: { default: () => ({ value: null }) },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * `<owner>/<repo>` of the app's repository on the forge.\n\t\t */\n\t\trepo: {\n\t\t\ttype: String,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/**\n\t\t * Target forge the feature-request deep-links are built against\n\t\t * (the Suggest CTA and the support dialog's feature-request URL).\n\t\t * Set `{type: 'github'}` (optionally with `baseUrl`) to switch\n\t\t * forge.\n\t\t *\n\t\t * @type {{type: 'codeberg'|'forgejo'|'gitea'|'github', baseUrl?: string}}\n\t\t */\n\t\tforge: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({ ...DEFAULT_FORGE }),\n\t\t},\n\n\t\t/**\n\t\t * Build-time feature manifest (alphabetical list rendered by CnFeaturesTab).\n\t\t *\n\t\t * @type {Array<{slug: string, title: string, summary: string, docsUrl: string}>}\n\t\t */\n\t\tfeatures: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * Admin opt-out flag — when true the entire view collapses to a\n\t\t * single \"disabled by admin\" empty state.\n\t\t */\n\t\tdisabled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Optional capability comparison. When supplied, the header toggle\n\t\t * gains a third stop that renders `CnCapabilityTable`: a searchable\n\t\t * table of the rows, grouped by feature or by area, with a\n\t\t * provided-by column naming which app or platform delivers each one.\n\t\t *\n\t\t * Shape: `{systems, areas, features?, providers?, capabilities}`. The\n\t\t * rows carry the competitor ratings they already carry, plus the\n\t\t * optional `provider`, `providerHow`, `feature` and\n\t\t * `featureConfidence` fields.\n\t\t *\n\t\t * When null (default) the toggle keeps its two stops and the view\n\t\t * renders exactly as it does for every app that passes nothing.\n\t\t *\n\t\t * Deprecated: publish the comparison on the app's docs site and link to it (as dossiq #3312 and pipelinq #2195 did). Still rendered; removal waits for a major version.\n\t\t * A development build warns once when it is set.\n\t\t *\n\t\t * @deprecated since 2.66.0, publish the comparison on the app's docs site and link to it.\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-the-in-app-capability-comparison-is-deprecated\n\t\t * @type {object|null}\n\t\t */\n\t\tcapabilityComparison: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Override the OpenBuilt sidebar CTA target. Pass an absolute URL or\n\t\t * a Nextcloud-relative path. When unset, defaults to the in-instance\n\t\t * OpenBuilt route at `/apps/openbuilt` (resolved via `generateUrl`).\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\topenbuiltUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Override the LLM-skills sidebar CTA target. Defaults to\n\t\t * `https://docs.conduction.nl/ai-skills`.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tllmSkillsUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Optional override for the Suggest CTA inside the sidebar. When\n\t\t * set the CTA renders as an anchor pointing at this URL —\n\t\t * appropriate when the app routes feature suggestions through a\n\t\t * public form, a Discord channel, or any non-forge target. When\n\t\t * empty (default) the CTA links the forge's feature-request\n\t\t * issue form derived from `repo` + `forge`.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tsuggestUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Optional URL of the app's public documentation site. When set,\n\t\t * an info banner is rendered above the card grid pointing users at\n\t\t * `<docs>` for full technical + user docs. Per-app — pipelinq\n\t\t * passes `https://pipelinq.conduction.nl`, decidesk passes its\n\t\t * own, etc. When empty (default) no banner renders.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tdocumentationUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Display name of the host app, used as the title of the bundled\n\t\t * `CnSupportDialog` and interpolated into its body copy. When\n\t\t * empty (default) the View derives a humanised label from `repo`\n\t\t * — `ConductionNL/openregister` becomes `Openregister` — which is\n\t\t * fine for stock apps and overridable for camelCased names.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tappName: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Kebab-case host-app id passed to `CnSupportDialog` (its\n\t\t * localStorage namespace). When empty (default) the View derives\n\t\t * it from `repo` — second segment lower-cased.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tappSlug: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Nextcloud App Store listing URL for the host app, threaded\n\t\t * through to `CnSupportDialog`. When empty (default) the View\n\t\t * derives `https://apps.nextcloud.com/apps/{appSlug}`.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tappStoreUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * URL the \"Suggest a feature\" CTA inside `CnSupportDialog` opens.\n\t\t * When empty (default) the View derives\n\t\t * `<forge-base>/{repo}/issues/new` from the `forge` prop. This is\n\t\t * intentionally separate from the prop-of-the-same-name on the\n\t\t * sidebar's Suggest CTA — that one opens the modal-driven flow;\n\t\t * this one is the \"open on the forge\" fallback used by the support\n\t\t * dialog, which is meant for the casual visitor rather than the\n\t\t * existing suggest-feature loop.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tfeatureRequestUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Donate-CTA URL passed to `CnSupportDialog`. Defaults to\n\t\t * ConductionNL's GitHub Sponsors page.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tdonateUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: DEFAULT_DONATE_URL,\n\t\t},\n\n\t\t/**\n\t\t * Business-support CTA URL passed to `CnSupportDialog`. Defaults\n\t\t * to the Conduction contact page.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tsupportUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: DEFAULT_SUPPORT_URL,\n\t\t},\n\n\t\t/**\n\t\t * Name rendered in the handwritten signature of `CnSupportDialog`.\n\t\t * Defaults to Ruben van der Linde.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tfounderName: {\n\t\t\ttype: String,\n\t\t\tdefault: DEFAULT_FOUNDER_NAME,\n\t\t},\n\n\t\t/**\n\t\t * Title shown after the handwritten signature in `CnSupportDialog`\n\t\t * (e.g. \"Founder\", \"Oprichter\").\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tfounderTitle: {\n\t\t\ttype: String,\n\t\t\tdefault: DEFAULT_FOUNDER_TITLE,\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tactiveView: 'features',\n\t\t\tshowSupportDialog: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The stops the header toggle cycles through. Without a capability\n\t\t * comparison this is the two-stop list every consumer has today, and\n\t\t * the toggle behaves exactly as it did.\n\t\t *\n\t\t * @return {Array<string>} The views, in cycle order.\n\t\t */\n\t\tavailableViews() {\n\t\t\treturn this.capabilityComparison\n\t\t\t\t? ['features', 'capabilities', 'roadmap']\n\t\t\t\t: ['features', 'roadmap']\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The view the toggle moves to next.\n\t\t */\n\t\tnextView() {\n\t\t\tconst views = this.availableViews\n\t\t\tconst index = views.indexOf(this.activeView)\n\t\t\treturn views[(index + 1) % views.length]\n\t\t},\n\n\t\theaderTitle() {\n\t\t\treturn this.viewTitle(this.activeView)\n\t\t},\n\n\t\ttoggleLabel() {\n\t\t\tif (this.nextView === 'capabilities') {\n\t\t\t\treturn t('nextcloud-vue', 'Show capabilities')\n\t\t\t}\n\t\t\treturn this.nextView === 'roadmap'\n\t\t\t\t? t('nextcloud-vue', 'Show roadmap')\n\t\t\t\t: t('nextcloud-vue', 'Show features')\n\t\t},\n\n\t\tsuggestLabel() {\n\t\t\treturn t('nextcloud-vue', 'Suggest feature')\n\t\t},\n\n\t\tdisabledTitle() {\n\t\t\treturn t('nextcloud-vue', 'This feature has been disabled by your administrator')\n\t\t},\n\n\t\tdisabledDescription() {\n\t\t\treturn t('nextcloud-vue', 'Contact your Nextcloud administrator to enable Features & Roadmap on this instance.')\n\t\t},\n\n\t\tresolvedOpenbuiltUrl() {\n\t\t\tif (this.openbuiltUrl) {\n\t\t\t\treturn this.openbuiltUrl\n\t\t\t}\n\t\t\treturn generateUrl(DEFAULT_OPENBUILT_PATH)\n\t\t},\n\n\t\tresolvedLlmSkillsUrl() {\n\t\t\treturn this.llmSkillsUrl || DEFAULT_LLM_SKILLS_URL\n\t\t},\n\n\t\tresolvedDocumentationUrl() {\n\t\t\treturn this.documentationUrl || ''\n\t\t},\n\n\t\trepoSlug() {\n\t\t\tif (!this.repo || !this.repo.includes('/')) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn this.repo.split('/')[1].toLowerCase()\n\t\t},\n\n\t\tresolvedAppSlug() {\n\t\t\treturn this.appSlug || this.repoSlug\n\t\t},\n\n\t\tresolvedAppName() {\n\t\t\tif (this.appName) {\n\t\t\t\treturn this.appName\n\t\t\t}\n\t\t\tconst slug = this.repoSlug\n\t\t\tif (!slug) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn slug.charAt(0).toUpperCase() + slug.slice(1)\n\t\t},\n\n\t\tresolvedAppStoreUrl() {\n\t\t\treturn this.appStoreUrl || (this.resolvedAppSlug\n\t\t\t\t? `https://apps.nextcloud.com/apps/${this.resolvedAppSlug}`\n\t\t\t\t: '')\n\t\t},\n\n\t\tresolvedFeatureRequestUrl() {\n\t\t\tif (this.featureRequestUrl) {\n\t\t\t\treturn this.featureRequestUrl\n\t\t\t}\n\t\t\tif (!this.repo) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\t// The feature-request issue FORM, not the blank new-issue page —\n\t\t\t// the form's structured fields replaced the removed in-product\n\t\t\t// suggestion modal.\n\t\t\treturn buildFeatureRequestUrl(this.forge, this.repo)\n\t\t},\n\n\t\t/**\n\t\t * The Suggest CTA's target: the host's `suggestUrl` override when\n\t\t * given (a public form, a Discord channel), else the forge's\n\t\t * feature-request issue form.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tresolvedSuggestUrl() {\n\t\t\treturn this.suggestUrl || this.resolvedFeatureRequestUrl\n\t\t},\n\n\t\tdocumentationUrlIsExternal() {\n\t\t\treturn /^https?:\\/\\//i.test(this.resolvedDocumentationUrl)\n\t\t},\n\n\t\tdocsNoteLeading() {\n\t\t\treturn t('nextcloud-vue', 'Looking for documentation? Visit')\n\t\t},\n\n\t\tdocsNoteLinkLabel() {\n\t\t\t// Strip protocol for a cleaner inline link label.\n\t\t\treturn this.resolvedDocumentationUrl.replace(/^https?:\\/\\//i, '')\n\t\t},\n\n\t\tdocsNoteTrailing() {\n\t\t\treturn t('nextcloud-vue', 'for all technical and user documentation.')\n\t\t},\n\t},\n\n\twatch: {\n\t\t// A comparison that goes away leaves the reader on a stop that no\n\t\t// longer exists: the panel's v-else-if still matches, so it renders an\n\t\t// empty CnCapabilityTable under a \"Capabilities\" heading until the\n\t\t// reader thinks to press the toggle.\n\t\tavailableViews(views) {\n\t\t\tif (!views.includes(this.activeView)) {\n\t\t\t\tthis.activeView = views[0]\n\t\t\t}\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.publishHoistedSidebar()\n\t\tif (this.capabilityComparison) {\n\t\t\twarnCapabilityComparisonDeprecated('CnFeaturesAndRoadmapView `capabilityComparison`')\n\t\t}\n\t},\n\n\tbeforeUnmount() {\n\t\t// Clear the holder so the hoisted sidebar disappears when the\n\t\t// user navigates away from this route. Mirrors CnIndexPage.\n\t\tif (this.cnHostsIndexSidebar && this.cnIndexSidebarConfig) {\n\t\t\tthis.cnIndexSidebarConfig.value = null\n\t\t}\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * @param {string} view One of the `availableViews`.\n\t\t * @return {string} The heading for that view.\n\t\t */\n\t\tviewTitle(view) {\n\t\t\tif (view === 'capabilities') {\n\t\t\t\treturn t('nextcloud-vue', 'Capabilities')\n\t\t\t}\n\t\t\treturn view === 'roadmap'\n\t\t\t\t? t('nextcloud-vue', 'Roadmap')\n\t\t\t\t: t('nextcloud-vue', 'Features')\n\t\t},\n\n\t\ttoggleView() {\n\t\t\tthis.activeView = this.nextView\n\t\t},\n\n\t\topenSupportDialog() {\n\t\t\tthis.showSupportDialog = true\n\t\t},\n\n\t\t/**\n\t\t * Publish the hoisted-sidebar config to `cnIndexSidebarConfig`\n\t\t * (same holder CnIndexPage uses) so CnAppRoot mounts the\n\t\t * CnFeaturesAndRoadmapSidebar at NcContent level. The sidebar\n\t\t * emits `suggest` when the user clicks the Suggest CTA inside\n\t\t * it; the view forwards that to `openSuggestModal()`. No-op\n\t\t * when there's no CnAppRoot ancestor.\n\t\t */\n\t\tpublishHoistedSidebar() {\n\t\t\tif (!this.cnHostsIndexSidebar || !this.cnIndexSidebarConfig) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.disabled) {\n\t\t\t\tthis.cnIndexSidebarConfig.value = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.cnIndexSidebarConfig.value = {\n\t\t\t\t// markRaw: see CnIndexPage's publishHoistedSidebar — the holder\n\t\t\t\t// is deeply reactive and would proxy the component definition.\n\t\t\t\tcomponent: markRaw(CnFeaturesAndRoadmapSidebar),\n\t\t\t\tprops: {\n\t\t\t\t\topenbuiltUrl: this.resolvedOpenbuiltUrl,\n\t\t\t\t\tllmSkillsUrl: this.resolvedLlmSkillsUrl,\n\t\t\t\t\t// Always a URL now (the removed in-product modal was the\n\t\t\t\t\t// only non-link path), so the sidebar CTA always renders\n\t\t\t\t\t// as an anchor and its `suggest` emit path stays idle.\n\t\t\t\t\tsuggestUrl: this.resolvedSuggestUrl,\n\t\t\t\t},\n\n\t\t\t\tlisteners: {\n\t\t\t\t\tsupport: () => this.openSupportDialog(),\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-features-and-roadmap-view {\n\tmax-width: 1200px;\n\tmargin: 0 auto;\n\tpadding: 24px 16px;\n}\n\n.cn-features-and-roadmap-view__header {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 12px;\n\tmargin-bottom: 24px;\n\tflex-wrap: wrap;\n\t/* Clear the Nextcloud navigation toggle button (44px wide, absolutely\n\t positioned at the left edge of .app-content) plus 12px breathing\n\t room. Only the HEADER shifts right — the panel below keeps full width. */\n\tpadding-inline-start: 56px;\n}\n\n.cn-features-and-roadmap-view__title {\n\tfont-size: 1.4em;\n\tmargin: 0;\n\tcolor: var(--color-main-text);\n}\n\n.cn-features-and-roadmap-view__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\talign-items: center;\n\tflex-wrap: wrap;\n}\n\n.cn-features-and-roadmap-view__panel {\n\tmin-height: 320px;\n}\n\n/* Underline the inline docs URL so it reads as a link inside the note\n body — NcNoteCard's default link styling drops the underline by\n inheriting from the host theme. */\n.cn-features-and-roadmap-view__docs-note :deep(a) {\n\tcolor: var(--color-primary-element);\n\ttext-decoration: underline;\n}\n\n.cn-features-and-roadmap-view__docs-note :deep(a:hover) {\n\ttext-decoration: underline;\n\topacity: 0.85;\n}\n</style>\n"],"names":["CnCapabilityTable","CnFeaturesTab","CnRoadmapTab","CnSupportDialog","t","CnFeaturesAndRoadmapSidebar"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"CnFeaturesAndRoadmapView.vue3.js","sources":["../../../../src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue?vue&type=template&id=7c44400f&lang.js"],"sourcesContent":["<template>\n\t<div class=\"cn-features-and-roadmap-view\">\n\t\t<div v-if=\"disabled\" class=\"cn-features-and-roadmap-view__disabled\">\n\t\t\t<NcEmptyContent :name=\"disabledTitle\" :description=\"disabledDescription\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<LockOutline :size=\"48\" />\n\t\t\t\t</template>\n\t\t\t</NcEmptyContent>\n\t\t</div>\n\t\t<template v-else>\n\t\t\t<header class=\"cn-features-and-roadmap-view__header\">\n\t\t\t\t<h2 class=\"cn-features-and-roadmap-view__title\">\n\t\t\t\t\t{{ headerTitle }}\n\t\t\t\t</h2>\n\t\t\t\t<div class=\"cn-features-and-roadmap-view__actions\">\n\t\t\t\t\t<NcButton @click=\"toggleView\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<TableSearch v-if=\"nextView === 'capabilities'\" :size=\"20\" />\n\t\t\t\t\t\t\t<RoadVariant v-else-if=\"nextView === 'roadmap'\" :size=\"20\" />\n\t\t\t\t\t\t\t<FormatListBulleted v-else :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ toggleLabel }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t\t<!-- Direct link to the forge's feature-request issue form\n\t\t\t\t\t (team decision 2026-09-04: the in-product suggestion\n\t\t\t\t\t modal is gone — the forge is where the conversation\n\t\t\t\t\t happens, in English). -->\n\t\t\t\t\t<NcButton\n\t\t\t\t\t\tv-if=\"resolvedSuggestUrl\"\n\t\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\t\t:href=\"resolvedSuggestUrl\"\n\t\t\t\t\t\ttarget=\"_blank\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<Plus :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ suggestLabel }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t</div>\n\t\t\t</header>\n\n\t\t\t<NcNoteCard\n\t\t\t\tv-if=\"resolvedDocumentationUrl\"\n\t\t\t\ttype=\"info\"\n\t\t\t\tclass=\"cn-features-and-roadmap-view__docs-note\">\n\t\t\t\t{{ docsNoteLeading }}\n\t\t\t\t<a\n\t\t\t\t\t:href=\"resolvedDocumentationUrl\"\n\t\t\t\t\t:target=\"documentationUrlIsExternal ? '_blank' : null\"\n\t\t\t\t\t:rel=\"documentationUrlIsExternal ? 'noopener noreferrer' : null\">{{ docsNoteLinkLabel }}</a>\n\t\t\t\t{{ docsNoteTrailing }}\n\t\t\t</NcNoteCard>\n\n\t\t\t<main class=\"cn-features-and-roadmap-view__panel\">\n\t\t\t\t<CnFeaturesTab v-if=\"activeView === 'features'\" :features=\"features\" />\n\t\t\t\t<CnCapabilityTable\n\t\t\t\t\tv-else-if=\"activeView === 'capabilities'\"\n\t\t\t\t\t:comparison=\"capabilityComparison\" />\n\t\t\t\t<CnRoadmapTab v-else :repo=\"repo\" />\n\t\t\t</main>\n\n\t\t\t<CnSupportDialog\n\t\t\t\tv-if=\"showSupportDialog\"\n\t\t\t\t:appName=\"resolvedAppName\"\n\t\t\t\t:appSlug=\"resolvedAppSlug\"\n\t\t\t\t:appStoreUrl=\"resolvedAppStoreUrl\"\n\t\t\t\t:featureRequestUrl=\"resolvedFeatureRequestUrl\"\n\t\t\t\t:donateUrl=\"donateUrl\"\n\t\t\t\t:supportUrl=\"supportUrl\"\n\t\t\t\t:founderName=\"founderName\"\n\t\t\t\t:founderTitle=\"founderTitle\"\n\t\t\t\t@close=\"showSupportDialog = false\" />\n\t\t</template>\n\t</div>\n</template>\n\n<script>\n/**\n * SPDX-License-Identifier: EUPL-1.2\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n *\n * CnFeaturesAndRoadmapView — route-level container for the Features &\n * Roadmap surface. Header carries the view-toggle and the primary\n * Suggest-feature CTA. Body is a card grid (Features OR Roadmap).\n *\n * The right-edge sidebar is hoisted to NcContent level via the same\n * `cnIndexSidebarConfig` provide mechanism CnIndexPage uses for its\n * CnIndexSidebar — that's the only place where Nextcloud's right-edge\n * sidebar slot renders correctly (next to NcAppContent, not inside it).\n * When mounted under CnAppRoot the view publishes\n * `CnFeaturesAndRoadmapSidebar` + props into the holder on mounted()\n * and clears it on beforeUnmount(). Sidebar carries four pitch\n * sections: Suggest, OpenBuilt, LLM, Support. Suggest renders as an\n * anchor to the forge's feature-request issue form (`resolvedSuggestUrl`\n * is always passed); Support emits `@support` (forwarded to a\n * freshly-mounted `CnSupportDialog`).\n *\n * Sidebar link targets are overridable via the `openbuiltUrl` and\n * `llmSkillsUrl` props. Defaults: in-instance `/apps/openbuilt` (via\n * `generateUrl`) and `https://docs.conduction.nl/ai-skills`.\n *\n * Spec: features-roadmap-component — Requirement \"CnFeaturesAndRoadmapView\".\n */\nimport { translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport { NcButton, NcEmptyContent, NcNoteCard } from '@nextcloud/vue'\nimport { markRaw } from 'vue'\nimport FormatListBulleted from 'vue-material-design-icons/FormatListBulleted.vue'\nimport LockOutline from 'vue-material-design-icons/LockOutline.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport RoadVariant from 'vue-material-design-icons/RoadVariant.vue'\nimport TableSearch from 'vue-material-design-icons/TableSearch.vue'\nimport CnCapabilityTable from '../CnCapabilityTable/CnCapabilityTable.vue'\nimport CnFeaturesAndRoadmapSidebar from '../CnFeaturesAndRoadmapSidebar/CnFeaturesAndRoadmapSidebar.vue'\nimport CnFeaturesTab from '../CnFeaturesTab/CnFeaturesTab.vue'\nimport CnRoadmapTab from '../CnRoadmapTab/CnRoadmapTab.vue'\nimport CnSupportDialog from '../CnSupportDialog/CnSupportDialog.vue'\nimport { buildFeatureRequestUrl, DEFAULT_FORGE } from '../../utils/forge.js'\n\nconst DEFAULT_OPENBUILT_PATH = '/apps/openbuilt'\nconst DEFAULT_LLM_SKILLS_URL = 'https://docs.conduction.nl/ai-skills'\nconst DEFAULT_DONATE_URL = 'https://github.com/sponsors/ConductionNL'\nconst DEFAULT_SUPPORT_URL = 'https://www.conduction.nl/support'\nconst DEFAULT_FOUNDER_NAME = 'Ruben van der Linde'\nconst DEFAULT_FOUNDER_TITLE = 'a founder of Conduction'\n\nexport default {\n\tname: 'CnFeaturesAndRoadmapView',\n\n\tcomponents: {\n\t\tNcButton,\n\t\tNcEmptyContent,\n\t\tNcNoteCard,\n\t\tFormatListBulleted,\n\t\tLockOutline,\n\t\tPlus,\n\t\tRoadVariant,\n\t\tTableSearch,\n\t\tCnCapabilityTable,\n\t\tCnFeaturesTab,\n\t\tCnRoadmapTab,\n\t\tCnSupportDialog,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * Set to `true` by CnAppRoot so descendants know the host will\n\t\t * mount a hoisted sidebar at NcContent level (see CnAppRoot.vue\n\t\t * `cnHostsIndexSidebar: true`). When `false` (no CnAppRoot\n\t\t * ancestor) the view skips publishing — there's no inline\n\t\t * fallback for this surface.\n\t\t */\n\t\tcnHostsIndexSidebar: { default: false },\n\t\t/**\n\t\t * Reactive holder published by CnAppRoot that backs the\n\t\t * hoisted-sidebar render in NcContent. Set value to\n\t\t * `{component, props, listeners}` to mount; set to `null` to\n\t\t * unmount. Same holder CnIndexPage uses.\n\t\t */\n\t\tcnIndexSidebarConfig: { default: () => ({ value: null }) },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * `<owner>/<repo>` of the app's repository on the forge.\n\t\t */\n\t\trepo: {\n\t\t\ttype: String,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/**\n\t\t * Target forge the feature-request deep-links are built against\n\t\t * (the Suggest CTA and the support dialog's feature-request URL).\n\t\t * Set `{type: 'github'}` (optionally with `baseUrl`) to switch\n\t\t * forge.\n\t\t *\n\t\t * @type {{type: 'codeberg'|'forgejo'|'gitea'|'github', baseUrl?: string}}\n\t\t */\n\t\tforge: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({ ...DEFAULT_FORGE }),\n\t\t},\n\n\t\t/**\n\t\t * Build-time feature manifest (alphabetical list rendered by CnFeaturesTab).\n\t\t *\n\t\t * @type {Array<{slug: string, title: string, summary: string, docsUrl: string}>}\n\t\t */\n\t\tfeatures: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * Admin opt-out flag — when true the entire view collapses to a\n\t\t * single \"disabled by admin\" empty state.\n\t\t */\n\t\tdisabled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Optional capability comparison. When supplied, the header toggle\n\t\t * gains a third stop that renders `CnCapabilityTable`: a searchable\n\t\t * table of the rows, grouped by feature or by area, with a\n\t\t * provided-by column naming which app or platform delivers each one.\n\t\t *\n\t\t * Shape: `{systems, areas, features?, providers?, capabilities}`. The\n\t\t * rows carry the competitor ratings they already carry, plus the\n\t\t * optional `provider`, `providerHow`, `feature` and\n\t\t * `featureConfidence` fields.\n\t\t *\n\t\t * When null (default) the toggle keeps its two stops and the view\n\t\t * renders exactly as it does for every app that passes nothing.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tcapabilityComparison: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Override the OpenBuilt sidebar CTA target. Pass an absolute URL or\n\t\t * a Nextcloud-relative path. When unset, defaults to the in-instance\n\t\t * OpenBuilt route at `/apps/openbuilt` (resolved via `generateUrl`).\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\topenbuiltUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Override the LLM-skills sidebar CTA target. Defaults to\n\t\t * `https://docs.conduction.nl/ai-skills`.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tllmSkillsUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Optional override for the Suggest CTA inside the sidebar. When\n\t\t * set the CTA renders as an anchor pointing at this URL —\n\t\t * appropriate when the app routes feature suggestions through a\n\t\t * public form, a Discord channel, or any non-forge target. When\n\t\t * empty (default) the CTA links the forge's feature-request\n\t\t * issue form derived from `repo` + `forge`.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tsuggestUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Optional URL of the app's public documentation site. When set,\n\t\t * an info banner is rendered above the card grid pointing users at\n\t\t * `<docs>` for full technical + user docs. Per-app — pipelinq\n\t\t * passes `https://pipelinq.conduction.nl`, decidesk passes its\n\t\t * own, etc. When empty (default) no banner renders.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tdocumentationUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Display name of the host app, used as the title of the bundled\n\t\t * `CnSupportDialog` and interpolated into its body copy. When\n\t\t * empty (default) the View derives a humanised label from `repo`\n\t\t * — `ConductionNL/openregister` becomes `Openregister` — which is\n\t\t * fine for stock apps and overridable for camelCased names.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tappName: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Kebab-case host-app id passed to `CnSupportDialog` (its\n\t\t * localStorage namespace). When empty (default) the View derives\n\t\t * it from `repo` — second segment lower-cased.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tappSlug: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Nextcloud App Store listing URL for the host app, threaded\n\t\t * through to `CnSupportDialog`. When empty (default) the View\n\t\t * derives `https://apps.nextcloud.com/apps/{appSlug}`.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tappStoreUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * URL the \"Suggest a feature\" CTA inside `CnSupportDialog` opens.\n\t\t * When empty (default) the View derives\n\t\t * `<forge-base>/{repo}/issues/new` from the `forge` prop. This is\n\t\t * intentionally separate from the prop-of-the-same-name on the\n\t\t * sidebar's Suggest CTA — that one opens the modal-driven flow;\n\t\t * this one is the \"open on the forge\" fallback used by the support\n\t\t * dialog, which is meant for the casual visitor rather than the\n\t\t * existing suggest-feature loop.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tfeatureRequestUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Donate-CTA URL passed to `CnSupportDialog`. Defaults to\n\t\t * ConductionNL's GitHub Sponsors page.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tdonateUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: DEFAULT_DONATE_URL,\n\t\t},\n\n\t\t/**\n\t\t * Business-support CTA URL passed to `CnSupportDialog`. Defaults\n\t\t * to the Conduction contact page.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tsupportUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: DEFAULT_SUPPORT_URL,\n\t\t},\n\n\t\t/**\n\t\t * Name rendered in the handwritten signature of `CnSupportDialog`.\n\t\t * Defaults to Ruben van der Linde.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tfounderName: {\n\t\t\ttype: String,\n\t\t\tdefault: DEFAULT_FOUNDER_NAME,\n\t\t},\n\n\t\t/**\n\t\t * Title shown after the handwritten signature in `CnSupportDialog`\n\t\t * (e.g. \"Founder\", \"Oprichter\").\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tfounderTitle: {\n\t\t\ttype: String,\n\t\t\tdefault: DEFAULT_FOUNDER_TITLE,\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tactiveView: 'features',\n\t\t\tshowSupportDialog: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The stops the header toggle cycles through. Without a capability\n\t\t * comparison this is the two-stop list every consumer has today, and\n\t\t * the toggle behaves exactly as it did.\n\t\t *\n\t\t * @return {Array<string>} The views, in cycle order.\n\t\t */\n\t\tavailableViews() {\n\t\t\treturn this.capabilityComparison\n\t\t\t\t? ['features', 'capabilities', 'roadmap']\n\t\t\t\t: ['features', 'roadmap']\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The view the toggle moves to next.\n\t\t */\n\t\tnextView() {\n\t\t\tconst views = this.availableViews\n\t\t\tconst index = views.indexOf(this.activeView)\n\t\t\treturn views[(index + 1) % views.length]\n\t\t},\n\n\t\theaderTitle() {\n\t\t\treturn this.viewTitle(this.activeView)\n\t\t},\n\n\t\ttoggleLabel() {\n\t\t\tif (this.nextView === 'capabilities') {\n\t\t\t\treturn t('nextcloud-vue', 'Show capabilities')\n\t\t\t}\n\t\t\treturn this.nextView === 'roadmap'\n\t\t\t\t? t('nextcloud-vue', 'Show roadmap')\n\t\t\t\t: t('nextcloud-vue', 'Show features')\n\t\t},\n\n\t\tsuggestLabel() {\n\t\t\treturn t('nextcloud-vue', 'Suggest feature')\n\t\t},\n\n\t\tdisabledTitle() {\n\t\t\treturn t('nextcloud-vue', 'This feature has been disabled by your administrator')\n\t\t},\n\n\t\tdisabledDescription() {\n\t\t\treturn t('nextcloud-vue', 'Contact your Nextcloud administrator to enable Features & Roadmap on this instance.')\n\t\t},\n\n\t\tresolvedOpenbuiltUrl() {\n\t\t\tif (this.openbuiltUrl) {\n\t\t\t\treturn this.openbuiltUrl\n\t\t\t}\n\t\t\treturn generateUrl(DEFAULT_OPENBUILT_PATH)\n\t\t},\n\n\t\tresolvedLlmSkillsUrl() {\n\t\t\treturn this.llmSkillsUrl || DEFAULT_LLM_SKILLS_URL\n\t\t},\n\n\t\tresolvedDocumentationUrl() {\n\t\t\treturn this.documentationUrl || ''\n\t\t},\n\n\t\trepoSlug() {\n\t\t\tif (!this.repo || !this.repo.includes('/')) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn this.repo.split('/')[1].toLowerCase()\n\t\t},\n\n\t\tresolvedAppSlug() {\n\t\t\treturn this.appSlug || this.repoSlug\n\t\t},\n\n\t\tresolvedAppName() {\n\t\t\tif (this.appName) {\n\t\t\t\treturn this.appName\n\t\t\t}\n\t\t\tconst slug = this.repoSlug\n\t\t\tif (!slug) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn slug.charAt(0).toUpperCase() + slug.slice(1)\n\t\t},\n\n\t\tresolvedAppStoreUrl() {\n\t\t\treturn this.appStoreUrl || (this.resolvedAppSlug\n\t\t\t\t? `https://apps.nextcloud.com/apps/${this.resolvedAppSlug}`\n\t\t\t\t: '')\n\t\t},\n\n\t\tresolvedFeatureRequestUrl() {\n\t\t\tif (this.featureRequestUrl) {\n\t\t\t\treturn this.featureRequestUrl\n\t\t\t}\n\t\t\tif (!this.repo) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\t// The feature-request issue FORM, not the blank new-issue page —\n\t\t\t// the form's structured fields replaced the removed in-product\n\t\t\t// suggestion modal.\n\t\t\treturn buildFeatureRequestUrl(this.forge, this.repo)\n\t\t},\n\n\t\t/**\n\t\t * The Suggest CTA's target: the host's `suggestUrl` override when\n\t\t * given (a public form, a Discord channel), else the forge's\n\t\t * feature-request issue form.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tresolvedSuggestUrl() {\n\t\t\treturn this.suggestUrl || this.resolvedFeatureRequestUrl\n\t\t},\n\n\t\tdocumentationUrlIsExternal() {\n\t\t\treturn /^https?:\\/\\//i.test(this.resolvedDocumentationUrl)\n\t\t},\n\n\t\tdocsNoteLeading() {\n\t\t\treturn t('nextcloud-vue', 'Looking for documentation? Visit')\n\t\t},\n\n\t\tdocsNoteLinkLabel() {\n\t\t\t// Strip protocol for a cleaner inline link label.\n\t\t\treturn this.resolvedDocumentationUrl.replace(/^https?:\\/\\//i, '')\n\t\t},\n\n\t\tdocsNoteTrailing() {\n\t\t\treturn t('nextcloud-vue', 'for all technical and user documentation.')\n\t\t},\n\t},\n\n\twatch: {\n\t\t// A comparison that goes away leaves the reader on a stop that no\n\t\t// longer exists: the panel's v-else-if still matches, so it renders an\n\t\t// empty CnCapabilityTable under a \"Capabilities\" heading until the\n\t\t// reader thinks to press the toggle.\n\t\tavailableViews(views) {\n\t\t\tif (!views.includes(this.activeView)) {\n\t\t\t\tthis.activeView = views[0]\n\t\t\t}\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.publishHoistedSidebar()\n\t},\n\n\tbeforeUnmount() {\n\t\t// Clear the holder so the hoisted sidebar disappears when the\n\t\t// user navigates away from this route. Mirrors CnIndexPage.\n\t\tif (this.cnHostsIndexSidebar && this.cnIndexSidebarConfig) {\n\t\t\tthis.cnIndexSidebarConfig.value = null\n\t\t}\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * @param {string} view One of the `availableViews`.\n\t\t * @return {string} The heading for that view.\n\t\t */\n\t\tviewTitle(view) {\n\t\t\tif (view === 'capabilities') {\n\t\t\t\treturn t('nextcloud-vue', 'Capabilities')\n\t\t\t}\n\t\t\treturn view === 'roadmap'\n\t\t\t\t? t('nextcloud-vue', 'Roadmap')\n\t\t\t\t: t('nextcloud-vue', 'Features')\n\t\t},\n\n\t\ttoggleView() {\n\t\t\tthis.activeView = this.nextView\n\t\t},\n\n\t\topenSupportDialog() {\n\t\t\tthis.showSupportDialog = true\n\t\t},\n\n\t\t/**\n\t\t * Publish the hoisted-sidebar config to `cnIndexSidebarConfig`\n\t\t * (same holder CnIndexPage uses) so CnAppRoot mounts the\n\t\t * CnFeaturesAndRoadmapSidebar at NcContent level. The sidebar\n\t\t * emits `suggest` when the user clicks the Suggest CTA inside\n\t\t * it; the view forwards that to `openSuggestModal()`. No-op\n\t\t * when there's no CnAppRoot ancestor.\n\t\t */\n\t\tpublishHoistedSidebar() {\n\t\t\tif (!this.cnHostsIndexSidebar || !this.cnIndexSidebarConfig) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.disabled) {\n\t\t\t\tthis.cnIndexSidebarConfig.value = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.cnIndexSidebarConfig.value = {\n\t\t\t\t// markRaw: see CnIndexPage's publishHoistedSidebar — the holder\n\t\t\t\t// is deeply reactive and would proxy the component definition.\n\t\t\t\tcomponent: markRaw(CnFeaturesAndRoadmapSidebar),\n\t\t\t\tprops: {\n\t\t\t\t\topenbuiltUrl: this.resolvedOpenbuiltUrl,\n\t\t\t\t\tllmSkillsUrl: this.resolvedLlmSkillsUrl,\n\t\t\t\t\t// Always a URL now (the removed in-product modal was the\n\t\t\t\t\t// only non-link path), so the sidebar CTA always renders\n\t\t\t\t\t// as an anchor and its `suggest` emit path stays idle.\n\t\t\t\t\tsuggestUrl: this.resolvedSuggestUrl,\n\t\t\t\t},\n\n\t\t\t\tlisteners: {\n\t\t\t\t\tsupport: () => this.openSupportDialog(),\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-features-and-roadmap-view {\n\tmax-width: 1200px;\n\tmargin: 0 auto;\n\tpadding: 24px 16px;\n}\n\n.cn-features-and-roadmap-view__header {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 12px;\n\tmargin-bottom: 24px;\n\tflex-wrap: wrap;\n\t/* Clear the Nextcloud navigation toggle button (44px wide, absolutely\n\t positioned at the left edge of .app-content) plus 12px breathing\n\t room. Only the HEADER shifts right — the panel below keeps full width. */\n\tpadding-inline-start: 56px;\n}\n\n.cn-features-and-roadmap-view__title {\n\tfont-size: 1.4em;\n\tmargin: 0;\n\tcolor: var(--color-main-text);\n}\n\n.cn-features-and-roadmap-view__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\talign-items: center;\n\tflex-wrap: wrap;\n}\n\n.cn-features-and-roadmap-view__panel {\n\tmin-height: 320px;\n}\n\n/* Underline the inline docs URL so it reads as a link inside the note\n body — NcNoteCard's default link styling drops the underline by\n inheriting from the host theme. */\n.cn-features-and-roadmap-view__docs-note :deep(a) {\n\tcolor: var(--color-primary-element);\n\ttext-decoration: underline;\n}\n\n.cn-features-and-roadmap-view__docs-note :deep(a:hover) {\n\ttext-decoration: underline;\n\topacity: 0.85;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createVNode","_Fragment","_createElementVNode","_toDisplayString","_createBlock","_createTextVNode"],"mappings":"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"}
1
+ {"version":3,"file":"CnFeaturesAndRoadmapView.vue3.js","sources":["../../../../src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue?vue&type=template&id=7c44400f&lang.js"],"sourcesContent":["<template>\n\t<div class=\"cn-features-and-roadmap-view\">\n\t\t<div v-if=\"disabled\" class=\"cn-features-and-roadmap-view__disabled\">\n\t\t\t<NcEmptyContent :name=\"disabledTitle\" :description=\"disabledDescription\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<LockOutline :size=\"48\" />\n\t\t\t\t</template>\n\t\t\t</NcEmptyContent>\n\t\t</div>\n\t\t<template v-else>\n\t\t\t<header class=\"cn-features-and-roadmap-view__header\">\n\t\t\t\t<h2 class=\"cn-features-and-roadmap-view__title\">\n\t\t\t\t\t{{ headerTitle }}\n\t\t\t\t</h2>\n\t\t\t\t<div class=\"cn-features-and-roadmap-view__actions\">\n\t\t\t\t\t<NcButton @click=\"toggleView\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<TableSearch v-if=\"nextView === 'capabilities'\" :size=\"20\" />\n\t\t\t\t\t\t\t<RoadVariant v-else-if=\"nextView === 'roadmap'\" :size=\"20\" />\n\t\t\t\t\t\t\t<FormatListBulleted v-else :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ toggleLabel }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t\t<!-- Direct link to the forge's feature-request issue form\n\t\t\t\t\t (team decision 2026-09-04: the in-product suggestion\n\t\t\t\t\t modal is gone — the forge is where the conversation\n\t\t\t\t\t happens, in English). -->\n\t\t\t\t\t<NcButton\n\t\t\t\t\t\tv-if=\"resolvedSuggestUrl\"\n\t\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\t\t:href=\"resolvedSuggestUrl\"\n\t\t\t\t\t\ttarget=\"_blank\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<Plus :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ suggestLabel }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t</div>\n\t\t\t</header>\n\n\t\t\t<NcNoteCard\n\t\t\t\tv-if=\"resolvedDocumentationUrl\"\n\t\t\t\ttype=\"info\"\n\t\t\t\tclass=\"cn-features-and-roadmap-view__docs-note\">\n\t\t\t\t{{ docsNoteLeading }}\n\t\t\t\t<a\n\t\t\t\t\t:href=\"resolvedDocumentationUrl\"\n\t\t\t\t\t:target=\"documentationUrlIsExternal ? '_blank' : null\"\n\t\t\t\t\t:rel=\"documentationUrlIsExternal ? 'noopener noreferrer' : null\">{{ docsNoteLinkLabel }}</a>\n\t\t\t\t{{ docsNoteTrailing }}\n\t\t\t</NcNoteCard>\n\n\t\t\t<main class=\"cn-features-and-roadmap-view__panel\">\n\t\t\t\t<CnFeaturesTab v-if=\"activeView === 'features'\" :features=\"features\" />\n\t\t\t\t<CnCapabilityTable\n\t\t\t\t\tv-else-if=\"activeView === 'capabilities'\"\n\t\t\t\t\t:comparison=\"capabilityComparison\" />\n\t\t\t\t<CnRoadmapTab v-else :repo=\"repo\" />\n\t\t\t</main>\n\n\t\t\t<CnSupportDialog\n\t\t\t\tv-if=\"showSupportDialog\"\n\t\t\t\t:appName=\"resolvedAppName\"\n\t\t\t\t:appSlug=\"resolvedAppSlug\"\n\t\t\t\t:appStoreUrl=\"resolvedAppStoreUrl\"\n\t\t\t\t:featureRequestUrl=\"resolvedFeatureRequestUrl\"\n\t\t\t\t:donateUrl=\"donateUrl\"\n\t\t\t\t:supportUrl=\"supportUrl\"\n\t\t\t\t:founderName=\"founderName\"\n\t\t\t\t:founderTitle=\"founderTitle\"\n\t\t\t\t@close=\"showSupportDialog = false\" />\n\t\t</template>\n\t</div>\n</template>\n\n<script>\n/**\n * SPDX-License-Identifier: EUPL-1.2\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n *\n * CnFeaturesAndRoadmapView — route-level container for the Features &\n * Roadmap surface. Header carries the view-toggle and the primary\n * Suggest-feature CTA. Body is a card grid (Features OR Roadmap).\n *\n * The right-edge sidebar is hoisted to NcContent level via the same\n * `cnIndexSidebarConfig` provide mechanism CnIndexPage uses for its\n * CnIndexSidebar — that's the only place where Nextcloud's right-edge\n * sidebar slot renders correctly (next to NcAppContent, not inside it).\n * When mounted under CnAppRoot the view publishes\n * `CnFeaturesAndRoadmapSidebar` + props into the holder on mounted()\n * and clears it on beforeUnmount(). Sidebar carries four pitch\n * sections: Suggest, OpenBuilt, LLM, Support. Suggest renders as an\n * anchor to the forge's feature-request issue form (`resolvedSuggestUrl`\n * is always passed); Support emits `@support` (forwarded to a\n * freshly-mounted `CnSupportDialog`).\n *\n * Sidebar link targets are overridable via the `openbuiltUrl` and\n * `llmSkillsUrl` props. Defaults: in-instance `/apps/openbuilt` (via\n * `generateUrl`) and `https://docs.conduction.nl/ai-skills`.\n *\n * Spec: features-roadmap-component — Requirement \"CnFeaturesAndRoadmapView\".\n */\nimport { translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport { NcButton, NcEmptyContent, NcNoteCard } from '@nextcloud/vue'\nimport { markRaw } from 'vue'\nimport FormatListBulleted from 'vue-material-design-icons/FormatListBulleted.vue'\nimport LockOutline from 'vue-material-design-icons/LockOutline.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport RoadVariant from 'vue-material-design-icons/RoadVariant.vue'\nimport TableSearch from 'vue-material-design-icons/TableSearch.vue'\nimport CnCapabilityTable from '../CnCapabilityTable/CnCapabilityTable.vue'\nimport CnFeaturesAndRoadmapSidebar from '../CnFeaturesAndRoadmapSidebar/CnFeaturesAndRoadmapSidebar.vue'\nimport CnFeaturesTab from '../CnFeaturesTab/CnFeaturesTab.vue'\nimport CnRoadmapTab from '../CnRoadmapTab/CnRoadmapTab.vue'\nimport CnSupportDialog from '../CnSupportDialog/CnSupportDialog.vue'\nimport { warnCapabilityComparisonDeprecated } from '../../utils/capabilityComparison.js'\nimport { buildFeatureRequestUrl, DEFAULT_FORGE } from '../../utils/forge.js'\n\nconst DEFAULT_OPENBUILT_PATH = '/apps/openbuilt'\nconst DEFAULT_LLM_SKILLS_URL = 'https://docs.conduction.nl/ai-skills'\nconst DEFAULT_DONATE_URL = 'https://github.com/sponsors/ConductionNL'\nconst DEFAULT_SUPPORT_URL = 'https://www.conduction.nl/support'\nconst DEFAULT_FOUNDER_NAME = 'Ruben van der Linde'\nconst DEFAULT_FOUNDER_TITLE = 'a founder of Conduction'\n\nexport default {\n\tname: 'CnFeaturesAndRoadmapView',\n\n\tcomponents: {\n\t\tNcButton,\n\t\tNcEmptyContent,\n\t\tNcNoteCard,\n\t\tFormatListBulleted,\n\t\tLockOutline,\n\t\tPlus,\n\t\tRoadVariant,\n\t\tTableSearch,\n\t\tCnCapabilityTable,\n\t\tCnFeaturesTab,\n\t\tCnRoadmapTab,\n\t\tCnSupportDialog,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * Set to `true` by CnAppRoot so descendants know the host will\n\t\t * mount a hoisted sidebar at NcContent level (see CnAppRoot.vue\n\t\t * `cnHostsIndexSidebar: true`). When `false` (no CnAppRoot\n\t\t * ancestor) the view skips publishing — there's no inline\n\t\t * fallback for this surface.\n\t\t */\n\t\tcnHostsIndexSidebar: { default: false },\n\t\t/**\n\t\t * Reactive holder published by CnAppRoot that backs the\n\t\t * hoisted-sidebar render in NcContent. Set value to\n\t\t * `{component, props, listeners}` to mount; set to `null` to\n\t\t * unmount. Same holder CnIndexPage uses.\n\t\t */\n\t\tcnIndexSidebarConfig: { default: () => ({ value: null }) },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * `<owner>/<repo>` of the app's repository on the forge.\n\t\t */\n\t\trepo: {\n\t\t\ttype: String,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/**\n\t\t * Target forge the feature-request deep-links are built against\n\t\t * (the Suggest CTA and the support dialog's feature-request URL).\n\t\t * Set `{type: 'github'}` (optionally with `baseUrl`) to switch\n\t\t * forge.\n\t\t *\n\t\t * @type {{type: 'codeberg'|'forgejo'|'gitea'|'github', baseUrl?: string}}\n\t\t */\n\t\tforge: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({ ...DEFAULT_FORGE }),\n\t\t},\n\n\t\t/**\n\t\t * Build-time feature manifest (alphabetical list rendered by CnFeaturesTab).\n\t\t *\n\t\t * @type {Array<{slug: string, title: string, summary: string, docsUrl: string}>}\n\t\t */\n\t\tfeatures: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * Admin opt-out flag — when true the entire view collapses to a\n\t\t * single \"disabled by admin\" empty state.\n\t\t */\n\t\tdisabled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Optional capability comparison. When supplied, the header toggle\n\t\t * gains a third stop that renders `CnCapabilityTable`: a searchable\n\t\t * table of the rows, grouped by feature or by area, with a\n\t\t * provided-by column naming which app or platform delivers each one.\n\t\t *\n\t\t * Shape: `{systems, areas, features?, providers?, capabilities}`. The\n\t\t * rows carry the competitor ratings they already carry, plus the\n\t\t * optional `provider`, `providerHow`, `feature` and\n\t\t * `featureConfidence` fields.\n\t\t *\n\t\t * When null (default) the toggle keeps its two stops and the view\n\t\t * renders exactly as it does for every app that passes nothing.\n\t\t *\n\t\t * Deprecated: publish the comparison on the app's docs site and link to it (as dossiq #3312 and pipelinq #2195 did). Still rendered; removal waits for a major version.\n\t\t * A development build warns once when it is set.\n\t\t *\n\t\t * @deprecated since 2.66.0, publish the comparison on the app's docs site and link to it.\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-the-in-app-capability-comparison-is-deprecated\n\t\t * @type {object|null}\n\t\t */\n\t\tcapabilityComparison: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Override the OpenBuilt sidebar CTA target. Pass an absolute URL or\n\t\t * a Nextcloud-relative path. When unset, defaults to the in-instance\n\t\t * OpenBuilt route at `/apps/openbuilt` (resolved via `generateUrl`).\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\topenbuiltUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Override the LLM-skills sidebar CTA target. Defaults to\n\t\t * `https://docs.conduction.nl/ai-skills`.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tllmSkillsUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Optional override for the Suggest CTA inside the sidebar. When\n\t\t * set the CTA renders as an anchor pointing at this URL —\n\t\t * appropriate when the app routes feature suggestions through a\n\t\t * public form, a Discord channel, or any non-forge target. When\n\t\t * empty (default) the CTA links the forge's feature-request\n\t\t * issue form derived from `repo` + `forge`.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tsuggestUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Optional URL of the app's public documentation site. When set,\n\t\t * an info banner is rendered above the card grid pointing users at\n\t\t * `<docs>` for full technical + user docs. Per-app — pipelinq\n\t\t * passes `https://pipelinq.conduction.nl`, decidesk passes its\n\t\t * own, etc. When empty (default) no banner renders.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tdocumentationUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Display name of the host app, used as the title of the bundled\n\t\t * `CnSupportDialog` and interpolated into its body copy. When\n\t\t * empty (default) the View derives a humanised label from `repo`\n\t\t * — `ConductionNL/openregister` becomes `Openregister` — which is\n\t\t * fine for stock apps and overridable for camelCased names.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tappName: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Kebab-case host-app id passed to `CnSupportDialog` (its\n\t\t * localStorage namespace). When empty (default) the View derives\n\t\t * it from `repo` — second segment lower-cased.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tappSlug: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Nextcloud App Store listing URL for the host app, threaded\n\t\t * through to `CnSupportDialog`. When empty (default) the View\n\t\t * derives `https://apps.nextcloud.com/apps/{appSlug}`.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tappStoreUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * URL the \"Suggest a feature\" CTA inside `CnSupportDialog` opens.\n\t\t * When empty (default) the View derives\n\t\t * `<forge-base>/{repo}/issues/new` from the `forge` prop. This is\n\t\t * intentionally separate from the prop-of-the-same-name on the\n\t\t * sidebar's Suggest CTA — that one opens the modal-driven flow;\n\t\t * this one is the \"open on the forge\" fallback used by the support\n\t\t * dialog, which is meant for the casual visitor rather than the\n\t\t * existing suggest-feature loop.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tfeatureRequestUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Donate-CTA URL passed to `CnSupportDialog`. Defaults to\n\t\t * ConductionNL's GitHub Sponsors page.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tdonateUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: DEFAULT_DONATE_URL,\n\t\t},\n\n\t\t/**\n\t\t * Business-support CTA URL passed to `CnSupportDialog`. Defaults\n\t\t * to the Conduction contact page.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tsupportUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: DEFAULT_SUPPORT_URL,\n\t\t},\n\n\t\t/**\n\t\t * Name rendered in the handwritten signature of `CnSupportDialog`.\n\t\t * Defaults to Ruben van der Linde.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tfounderName: {\n\t\t\ttype: String,\n\t\t\tdefault: DEFAULT_FOUNDER_NAME,\n\t\t},\n\n\t\t/**\n\t\t * Title shown after the handwritten signature in `CnSupportDialog`\n\t\t * (e.g. \"Founder\", \"Oprichter\").\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tfounderTitle: {\n\t\t\ttype: String,\n\t\t\tdefault: DEFAULT_FOUNDER_TITLE,\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tactiveView: 'features',\n\t\t\tshowSupportDialog: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The stops the header toggle cycles through. Without a capability\n\t\t * comparison this is the two-stop list every consumer has today, and\n\t\t * the toggle behaves exactly as it did.\n\t\t *\n\t\t * @return {Array<string>} The views, in cycle order.\n\t\t */\n\t\tavailableViews() {\n\t\t\treturn this.capabilityComparison\n\t\t\t\t? ['features', 'capabilities', 'roadmap']\n\t\t\t\t: ['features', 'roadmap']\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The view the toggle moves to next.\n\t\t */\n\t\tnextView() {\n\t\t\tconst views = this.availableViews\n\t\t\tconst index = views.indexOf(this.activeView)\n\t\t\treturn views[(index + 1) % views.length]\n\t\t},\n\n\t\theaderTitle() {\n\t\t\treturn this.viewTitle(this.activeView)\n\t\t},\n\n\t\ttoggleLabel() {\n\t\t\tif (this.nextView === 'capabilities') {\n\t\t\t\treturn t('nextcloud-vue', 'Show capabilities')\n\t\t\t}\n\t\t\treturn this.nextView === 'roadmap'\n\t\t\t\t? t('nextcloud-vue', 'Show roadmap')\n\t\t\t\t: t('nextcloud-vue', 'Show features')\n\t\t},\n\n\t\tsuggestLabel() {\n\t\t\treturn t('nextcloud-vue', 'Suggest feature')\n\t\t},\n\n\t\tdisabledTitle() {\n\t\t\treturn t('nextcloud-vue', 'This feature has been disabled by your administrator')\n\t\t},\n\n\t\tdisabledDescription() {\n\t\t\treturn t('nextcloud-vue', 'Contact your Nextcloud administrator to enable Features & Roadmap on this instance.')\n\t\t},\n\n\t\tresolvedOpenbuiltUrl() {\n\t\t\tif (this.openbuiltUrl) {\n\t\t\t\treturn this.openbuiltUrl\n\t\t\t}\n\t\t\treturn generateUrl(DEFAULT_OPENBUILT_PATH)\n\t\t},\n\n\t\tresolvedLlmSkillsUrl() {\n\t\t\treturn this.llmSkillsUrl || DEFAULT_LLM_SKILLS_URL\n\t\t},\n\n\t\tresolvedDocumentationUrl() {\n\t\t\treturn this.documentationUrl || ''\n\t\t},\n\n\t\trepoSlug() {\n\t\t\tif (!this.repo || !this.repo.includes('/')) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn this.repo.split('/')[1].toLowerCase()\n\t\t},\n\n\t\tresolvedAppSlug() {\n\t\t\treturn this.appSlug || this.repoSlug\n\t\t},\n\n\t\tresolvedAppName() {\n\t\t\tif (this.appName) {\n\t\t\t\treturn this.appName\n\t\t\t}\n\t\t\tconst slug = this.repoSlug\n\t\t\tif (!slug) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn slug.charAt(0).toUpperCase() + slug.slice(1)\n\t\t},\n\n\t\tresolvedAppStoreUrl() {\n\t\t\treturn this.appStoreUrl || (this.resolvedAppSlug\n\t\t\t\t? `https://apps.nextcloud.com/apps/${this.resolvedAppSlug}`\n\t\t\t\t: '')\n\t\t},\n\n\t\tresolvedFeatureRequestUrl() {\n\t\t\tif (this.featureRequestUrl) {\n\t\t\t\treturn this.featureRequestUrl\n\t\t\t}\n\t\t\tif (!this.repo) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\t// The feature-request issue FORM, not the blank new-issue page —\n\t\t\t// the form's structured fields replaced the removed in-product\n\t\t\t// suggestion modal.\n\t\t\treturn buildFeatureRequestUrl(this.forge, this.repo)\n\t\t},\n\n\t\t/**\n\t\t * The Suggest CTA's target: the host's `suggestUrl` override when\n\t\t * given (a public form, a Discord channel), else the forge's\n\t\t * feature-request issue form.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tresolvedSuggestUrl() {\n\t\t\treturn this.suggestUrl || this.resolvedFeatureRequestUrl\n\t\t},\n\n\t\tdocumentationUrlIsExternal() {\n\t\t\treturn /^https?:\\/\\//i.test(this.resolvedDocumentationUrl)\n\t\t},\n\n\t\tdocsNoteLeading() {\n\t\t\treturn t('nextcloud-vue', 'Looking for documentation? Visit')\n\t\t},\n\n\t\tdocsNoteLinkLabel() {\n\t\t\t// Strip protocol for a cleaner inline link label.\n\t\t\treturn this.resolvedDocumentationUrl.replace(/^https?:\\/\\//i, '')\n\t\t},\n\n\t\tdocsNoteTrailing() {\n\t\t\treturn t('nextcloud-vue', 'for all technical and user documentation.')\n\t\t},\n\t},\n\n\twatch: {\n\t\t// A comparison that goes away leaves the reader on a stop that no\n\t\t// longer exists: the panel's v-else-if still matches, so it renders an\n\t\t// empty CnCapabilityTable under a \"Capabilities\" heading until the\n\t\t// reader thinks to press the toggle.\n\t\tavailableViews(views) {\n\t\t\tif (!views.includes(this.activeView)) {\n\t\t\t\tthis.activeView = views[0]\n\t\t\t}\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.publishHoistedSidebar()\n\t\tif (this.capabilityComparison) {\n\t\t\twarnCapabilityComparisonDeprecated('CnFeaturesAndRoadmapView `capabilityComparison`')\n\t\t}\n\t},\n\n\tbeforeUnmount() {\n\t\t// Clear the holder so the hoisted sidebar disappears when the\n\t\t// user navigates away from this route. Mirrors CnIndexPage.\n\t\tif (this.cnHostsIndexSidebar && this.cnIndexSidebarConfig) {\n\t\t\tthis.cnIndexSidebarConfig.value = null\n\t\t}\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * @param {string} view One of the `availableViews`.\n\t\t * @return {string} The heading for that view.\n\t\t */\n\t\tviewTitle(view) {\n\t\t\tif (view === 'capabilities') {\n\t\t\t\treturn t('nextcloud-vue', 'Capabilities')\n\t\t\t}\n\t\t\treturn view === 'roadmap'\n\t\t\t\t? t('nextcloud-vue', 'Roadmap')\n\t\t\t\t: t('nextcloud-vue', 'Features')\n\t\t},\n\n\t\ttoggleView() {\n\t\t\tthis.activeView = this.nextView\n\t\t},\n\n\t\topenSupportDialog() {\n\t\t\tthis.showSupportDialog = true\n\t\t},\n\n\t\t/**\n\t\t * Publish the hoisted-sidebar config to `cnIndexSidebarConfig`\n\t\t * (same holder CnIndexPage uses) so CnAppRoot mounts the\n\t\t * CnFeaturesAndRoadmapSidebar at NcContent level. The sidebar\n\t\t * emits `suggest` when the user clicks the Suggest CTA inside\n\t\t * it; the view forwards that to `openSuggestModal()`. No-op\n\t\t * when there's no CnAppRoot ancestor.\n\t\t */\n\t\tpublishHoistedSidebar() {\n\t\t\tif (!this.cnHostsIndexSidebar || !this.cnIndexSidebarConfig) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.disabled) {\n\t\t\t\tthis.cnIndexSidebarConfig.value = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.cnIndexSidebarConfig.value = {\n\t\t\t\t// markRaw: see CnIndexPage's publishHoistedSidebar — the holder\n\t\t\t\t// is deeply reactive and would proxy the component definition.\n\t\t\t\tcomponent: markRaw(CnFeaturesAndRoadmapSidebar),\n\t\t\t\tprops: {\n\t\t\t\t\topenbuiltUrl: this.resolvedOpenbuiltUrl,\n\t\t\t\t\tllmSkillsUrl: this.resolvedLlmSkillsUrl,\n\t\t\t\t\t// Always a URL now (the removed in-product modal was the\n\t\t\t\t\t// only non-link path), so the sidebar CTA always renders\n\t\t\t\t\t// as an anchor and its `suggest` emit path stays idle.\n\t\t\t\t\tsuggestUrl: this.resolvedSuggestUrl,\n\t\t\t\t},\n\n\t\t\t\tlisteners: {\n\t\t\t\t\tsupport: () => this.openSupportDialog(),\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-features-and-roadmap-view {\n\tmax-width: 1200px;\n\tmargin: 0 auto;\n\tpadding: 24px 16px;\n}\n\n.cn-features-and-roadmap-view__header {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 12px;\n\tmargin-bottom: 24px;\n\tflex-wrap: wrap;\n\t/* Clear the Nextcloud navigation toggle button (44px wide, absolutely\n\t positioned at the left edge of .app-content) plus 12px breathing\n\t room. Only the HEADER shifts right — the panel below keeps full width. */\n\tpadding-inline-start: 56px;\n}\n\n.cn-features-and-roadmap-view__title {\n\tfont-size: 1.4em;\n\tmargin: 0;\n\tcolor: var(--color-main-text);\n}\n\n.cn-features-and-roadmap-view__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\talign-items: center;\n\tflex-wrap: wrap;\n}\n\n.cn-features-and-roadmap-view__panel {\n\tmin-height: 320px;\n}\n\n/* Underline the inline docs URL so it reads as a link inside the note\n body — NcNoteCard's default link styling drops the underline by\n inheriting from the host theme. */\n.cn-features-and-roadmap-view__docs-note :deep(a) {\n\tcolor: var(--color-primary-element);\n\ttext-decoration: underline;\n}\n\n.cn-features-and-roadmap-view__docs-note :deep(a:hover) {\n\ttext-decoration: underline;\n\topacity: 0.85;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createVNode","_Fragment","_createElementVNode","_toDisplayString","_createBlock","_createTextVNode"],"mappings":"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"}
@@ -80,7 +80,7 @@ var script = {
80
80
  * Persisted widget content: `{title, subtitle, backgroundImageUrl,
81
81
  * backgroundImageFileId, backgroundColor, overlayMode, overlayColor,
82
82
  * overlayOpacity, textColor, textAlign, verticalAlign, height, cta,
83
- * greeting, showDate, plain, ground, views}`. All fields are optional except
83
+ * greeting, showDate, kicker, emblem, plain, ground, views}`. All fields are optional except
84
84
  * `title` (or `greeting`); unknown enum values collapse to documented
85
85
  * defaults and the renderer never throws. `views` is
86
86
  * `{ ariaLabel?, options: [{ label, route, params? } | { label, view }] }`:
@@ -91,7 +91,12 @@ var script = {
91
91
  * own. `ground: true`
92
92
  * draws the greeting on the page ground: the plain look with no
93
93
  * padding, a 32px heading and the date line 6px above it (a dashboard
94
- * also drops the widget's card for it).
94
+ * also drops the widget's card for it). A ground greeting takes its
95
+ * own height rather than its grid cell's, so a view switch sits on
96
+ * the heading's line. `kicker` (an i18n key) prefixes the date line, as
97
+ * "Customer contact · Tuesday 6 October". `emblem` (a URL, or `true`
98
+ * for the theme's `--nldesign-emblem-url`) draws the emblem beside the
99
+ * date line and heading, 52px high (`--cn-header-emblem-size`).
95
100
  *
96
101
  * @type {object}
97
102
  */
@@ -336,13 +341,30 @@ var script = {
336
341
  },
337
342
 
338
343
  /**
339
- * Today's date written out ("Monday 5 October 2026"), or '' when
340
- * `showDate` is off.
344
+ * The kicker line above the heading: `content.kicker` (through the
345
+ * host translate function, e.g. "Customer contact") and today's date
346
+ * written out ("Monday 5 October 2026") when `showDate` is on, joined
347
+ * by " · ". '' when neither is set. Without `kicker` this is the date
348
+ * line as before.
341
349
  *
342
350
  * @return {string}
343
351
  * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-greeting-header
344
352
  */
345
353
  dateLine() {
354
+ const kicker = this.content && typeof this.content.kicker === 'string' && this.content.kicker !== ''
355
+ ? this.effectiveTranslate(this.content.kicker)
356
+ : '';
357
+ const date = this.dateText;
358
+ return [kicker, date].filter((part) => part !== '').join(' · ')
359
+ },
360
+
361
+ /**
362
+ * Today's date written out, or '' when `showDate` is off.
363
+ *
364
+ * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-greeting-kicker-can-carry-a-prefix
365
+ * @return {string}
366
+ */
367
+ dateText() {
346
368
  if (!this.content || this.content.showDate !== true) {
347
369
  return ''
348
370
  }
@@ -374,6 +396,21 @@ var script = {
374
396
  }
375
397
  },
376
398
 
399
+ /**
400
+ * The emblem beside the greeting: the URL `content.emblem` names,
401
+ * `true` for the theme's emblem, or null without the key.
402
+ *
403
+ * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-greeting-can-carry-the-emblem
404
+ * @return {string|true|null}
405
+ */
406
+ emblem() {
407
+ const value = this.content && this.content.emblem;
408
+ if (value === true) {
409
+ return true
410
+ }
411
+ return typeof value === 'string' && value !== '' ? resolveImageUrl(value) : null
412
+ },
413
+
377
414
  /** Whether a non-empty subtitle is set. */
378
415
  hasSubtitle() {
379
416
  return this.subtitle !== ''
@@ -514,7 +551,12 @@ var script = {
514
551
  return this.isLightColor(this.backgroundColor) ? '#000000' : '#ffffff'
515
552
  },
516
553
 
517
- /** Inline style for the banner wrapper. */
554
+ /**
555
+ * Inline style for the banner wrapper.
556
+ *
557
+ * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-ground-greeting-lines-its-switch-up-with-the-heading
558
+ * @return {object}
559
+ */
518
560
  wrapperStyle() {
519
561
  const style = {
520
562
  position: 'relative',
@@ -523,7 +565,11 @@ var script = {
523
565
  // widget. A fixed pixel height ignored the cell, which made the
524
566
  // banner un-resizable and made it overflow/scroll inside a
525
567
  // smaller cell. The grid controls the size now.
526
- height: '100%',
568
+ // On the page ground there is no banner to fill the cell with:
569
+ // the greeting takes its own height, so the view switch lines
570
+ // up with the heading instead of the bottom of a taller cell,
571
+ // and a size-to-content cell can shrink to it.
572
+ height: this.isGround ? 'auto' : '100%',
527
573
  overflow: 'hidden',
528
574
  'background-color': this.backgroundColor,
529
575
  };
@@ -559,6 +605,7 @@ var script = {
559
605
  * Inline style for the content flex container.
560
606
  *
561
607
  * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-greeting-header
608
+ * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-ground-greeting-lines-its-switch-up-with-the-heading
562
609
  */
563
610
  contentStyle() {
564
611
  return {
@@ -567,7 +614,7 @@ var script = {
567
614
  // Beside a view switch the content shares the row instead of
568
615
  // taking the whole width (see .cn-header-widget--with-views).
569
616
  width: this.viewOptions.length > 0 ? 'auto' : '100%',
570
- height: '100%',
617
+ height: this.isGround ? 'auto' : '100%',
571
618
  display: 'flex',
572
619
  'flex-direction': 'column',
573
620
  'align-items': this.flexAlignFromTextAlign,