Skip to content

Commit 0020132

Browse files
authored
docs(spec): trace 64 frontend methods to their spec, and sync the spec seven tags already pointed at (#471)
gate-16 spec-coverage 64 -> PASS, measured with hydra-gates 651e5c5 at the CI scope (--scope-to-diff --base origin/beta). gate-46 spec-anchor-existence stays PASS, so every anchor added here resolves to a heading that exists. 64 changed frontend methods across 24 files carried no @SPEC. Each now names the requirement it serves — facet views and the facet store to gemma-faceted-search, the SBOM panel to sbom-import, the suite wizard to suite-wizard, the portfolio helpers to portfolio-rationalization-time, the view store to view-enrichment-api, the review modals to catalog-ratings, and so on. No tag was added without reading the method and the requirement it points at. openspec/specs/realtime-updates-ui/ is NEW here, and that is the real find. Change `adopt-live-updates-ui` declares "Affected specs: realtime-updates-ui (new)" and shipped src/composables/useLiveCollections.js plus its seven consumers — but the delta was never synced into openspec/specs/. SEVEN @SPEC tags across six source files have been pointing at a spec that does not exist. gate-46 never caught it because gate-46 validates ANCHORS (#fragment), not bare file targets; an audit of every `@spec openspec/specs/<x>/spec.md` in src/ found this one and only this one dangling. The delta is promoted verbatim — its requirement, its three scenarios and its author's reason-bearing `@e2e exclude` are unchanged; the only edits are the title line and the delta's "## ADDED Requirements" heading becoming "## Requirements", plus a Purpose recording where it came from. I did not author that exclusion. I nearly made this worse: the first draft of this change copied the dangling `@spec openspec/specs/realtime-updates-ui/spec.md` onto useLiveCollections() itself. Checking the target existed before trusting it is what turned a propagated broken reference into a fixed one. Can-fail: reverting the 22 annotated files takes gate-16 from PASS back to 51; the run before any of this work reported 64 on the same package. vitest 215/215. gate-46 PASS. No other gate count moved.
1 parent f8c32f7 commit 0020132

25 files changed

Lines changed: 168 additions & 1 deletion
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# realtime-updates-ui Specification
2+
3+
## Purpose
4+
Live-update subscription behaviour for Softwarecatalog views that render from the
5+
`createObjectStore`-based object store. Synced from change `adopt-live-updates-ui`,
6+
which shipped `src/composables/useLiveCollections.js` and its seven consumers but
7+
was never synced into `openspec/specs/`.
8+
9+
## Requirements
10+
11+
### Requirement: Store-rendered views MUST subscribe to live updates for their scope
12+
13+
Views that render from Softwarecatalog's `createObjectStore`-based object store MUST subscribe to
14+
live updates for the data they display: collection-scoped views subscribe to
15+
`or-collection-{register-slug}-{schema-slug}` per rendered object type, object-scoped views
16+
subscribe to `or-object-{uuid}`. Subscriptions MUST be re-scoped when the viewed scope
17+
changes and released when the view is destroyed. Events are refetch HINTS only: views MUST
18+
refetch through their existing fetch paths and MUST NOT patch rendered state from an event
19+
payload.
20+
21+
@e2e exclude Requires a second concurrent authenticated session plus a notify_push (or poll-tick) round-trip; covered by the shared library's transport tests and manual two-browser verification.
22+
23+
#### Scenario: Module view refreshes when a rendered collection changes elsewhere
24+
25+
- **GIVEN** a module view (vulnerabilities, compliance matrix, license posture, lifecycle
26+
roadmap, or organisaties index) is open
27+
- **WHEN** another user creates, updates or deletes an object of a type the view renders
28+
- **THEN** the view receives the `or-collection-{register}-{schema}` hint, the plugin
29+
re-runs `fetchCollection` with the last-used params, and the view's `getCollection`-backed
30+
computeds re-render the fresh data without a manual refresh
31+
32+
#### Scenario: Subscription waits for lazy type registration
33+
34+
- **GIVEN** a module view mounts before its `loadData()` has registered its object types
35+
- **WHEN** the registration lands in the store's `objectTypeRegistry`
36+
- **THEN** the reactive `enabled` gate flips and the subscription attaches — no subscribe
37+
call is attempted against an unregistered type
38+
39+
#### Scenario: Subscription released on destroy
40+
41+
- **GIVEN** live subscriptions are active for a module view
42+
- **WHEN** the user navigates away and the component scope is disposed
43+
- **THEN** every subscription is released via the composable's scope-bound lifecycle

src/components/cards/OrganisatieCard.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ export default {
240240
* a custom cardComponent like this one must emit it explicitly —
241241
* without it, clicking an organisation card was a no-op.
242242
* @return {void}
243+
* @spec openspec/specs/fe-organizations/spec.md
243244
*/
244245
handleCardClick() {
245246
this.$emit('click', this.item)

src/components/reviews/ReviewsPanel.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ export default {
176176
* Open the submit-review modal.
177177
*
178178
* @return {void}
179+
* @spec openspec/specs/catalog-ratings/spec.md
179180
*/
180181
openSubmitModal() {
181182
this.showSubmitModal = true

src/components/sbom/SbomComponentsPanel.vue

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ export default {
206206
* The moduleVersie's raw data bag.
207207
*
208208
* @return {object} The property bag.
209+
* @spec openspec/specs/sbom-import/spec.md#requirement-imported-components-persist-as-openregister-objects-scoped-to-a-moduleversie
209210
*/
210211
moduleVersieData() {
211212
if (!this.moduleVersie) {
@@ -243,6 +244,7 @@ export default {
243244
* All kwetsbaarheid records — the vulnerability-match candidate set.
244245
*
245246
* @return {Array<object>} The kwetsbaarheid records.
247+
* @spec openspec/specs/sbom-import/spec.md#requirement-components-are-matched-against-existing-kwetsbaarheden-without-external-calls
246248
*/
247249
kwetsbaarheden() {
248250
return objectStore.getCollection('kwetsbaarheid')?.results || []
@@ -263,6 +265,7 @@ export default {
263265
* Display rows for CnDataTable: name/version/purl/licenses plus match badge counts.
264266
*
265267
* @return {Array<object>} The table rows.
268+
* @spec openspec/specs/sbom-import/spec.md#requirement-the-module-version-detail-page-shows-imported-components-with-summary-counts
266269
*/
267270
rows() {
268271
return this.matches.rows.map(({ component, confirmed, possible }) => {
@@ -283,6 +286,7 @@ export default {
283286
* Total imported component count.
284287
*
285288
* @return {number} The count.
289+
* @spec openspec/specs/sbom-import/spec.md#requirement-the-module-version-detail-page-shows-imported-components-with-summary-counts
286290
*/
287291
totalComponents() {
288292
return this.components.length
@@ -292,6 +296,7 @@ export default {
292296
* Distinct, non-empty license count across the imported set.
293297
*
294298
* @return {number} The count.
299+
* @spec openspec/specs/sbom-import/spec.md#requirement-the-module-version-detail-page-shows-imported-components-with-summary-counts
295300
*/
296301
distinctLicenseCount() {
297302
const set = new Set()
@@ -313,6 +318,7 @@ export default {
313318
* deduplicated) across the whole component list.
314319
*
315320
* @return {number} The count.
321+
* @spec openspec/specs/sbom-import/spec.md#requirement-components-are-matched-against-existing-kwetsbaarheden-without-external-calls
316322
*/
317323
matchedVulnerabilityCount() {
318324
return this.matches.matchedVulnerabilityCount
@@ -378,6 +384,7 @@ export default {
378384
*
379385
* @param {string} type Object type slug.
380386
* @return {Promise<void>} Resolves once fetched.
387+
* @spec openspec/specs/sbom-import/spec.md#requirement-the-module-version-detail-page-shows-imported-components-with-summary-counts
381388
*/
382389
async fetchType(type) {
383390
if (typeof objectStore.registerObjectType === 'function'
@@ -402,6 +409,7 @@ export default {
402409
*
403410
* @param {Event} event The file input change event.
404411
* @return {void}
412+
* @spec openspec/specs/sbom-import/spec.md#requirement-uploaded-sbom-files-are-bounded-in-size-and-json-only
405413
*/
406414
handleFileSelect(event) {
407415
const file = event.target.files[0]
@@ -417,6 +425,7 @@ export default {
417425
*
418426
* @param {number} bytes The size in bytes.
419427
* @return {string} A human-readable size.
428+
* @spec openspec/specs/sbom-import/spec.md#requirement-uploaded-sbom-files-are-bounded-in-size-and-json-only
420429
*/
421430
formatFileSize(bytes) {
422431
if (!bytes) {

src/composables/useLiveCollections.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,15 @@
2626
import { computed } from 'vue'
2727
import { useObjectSubscription } from '@conduction/nextcloud-vue'
2828

29+
/**
30+
* Subscribe a component to live updates for a static list of object types.
31+
*
32+
* @param {object} objectStore The app's createObjectStore-based store instance.
33+
* @param {Array<string>} types Object type slugs to subscribe to (static list).
34+
* @return {void}
35+
*
36+
* @spec openspec/specs/realtime-updates-ui/spec.md
37+
*/
2938
export function useLiveCollections(objectStore, types) {
3039
for (const type of types) {
3140
useObjectSubscription(objectStore, type, null, {

src/dialogs/SuiteWizard/Step1Details.vue

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,21 @@ export default {
6464
* `onField` so `_step1Valid` recomputes on every keystroke.
6565
*
6666
* @return {string} The current long description.
67+
* @spec openspec/specs/suite-wizard/spec.md#requirement-the-wizard-shall-guide-suite-creation-through-details-application-attachment-and-confirmation-steps
6768
*/
6869
beschrijvingLangModel: {
70+
/**
71+
* @return {string} The current long description.
72+
* @spec openspec/specs/suite-wizard/spec.md#requirement-the-wizard-shall-guide-suite-creation-through-details-application-attachment-and-confirmation-steps
73+
*/
6974
get() {
7075
return this.payload.beschrijvingLang || ''
7176
},
77+
/**
78+
* @param {string} value The new long description.
79+
* @return {void}
80+
* @spec openspec/specs/suite-wizard/spec.md#requirement-the-wizard-shall-guide-suite-creation-through-details-application-attachment-and-confirmation-steps
81+
*/
7282
set(value) {
7383
this.onField('beschrijvingLang', value)
7484
},
@@ -86,6 +96,7 @@ export default {
8696
* @param {string} key The stepData field to update.
8797
* @param {string} value The new value.
8898
* @return {void}
99+
* @spec openspec/specs/suite-wizard/spec.md#requirement-the-wizard-must-require-at-least-one-attached-application-before-advancing-past-the-applications-step
89100
*/
90101
onField(key, value) {
91102
const merged = { ...this.payload, [key]: value }

src/dialogs/SuiteWizard/Step2Applications.vue

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ export default {
7373
* Existing modules, mapped to `NcSelect` option shape.
7474
*
7575
* @return {Array<{uuid: string, label: string, raw: object}>}
76+
* @spec openspec/specs/suite-wizard/spec.md#requirement-the-wizard-shall-guide-suite-creation-through-details-application-attachment-and-confirmation-steps
7677
*/
7778
applicationOptions() {
7879
// `getCollection()` returns the paginated ENVELOPE ({ results, ... }),
@@ -89,6 +90,7 @@ export default {
8990
* so re-entering this step (e.g. via "Back") keeps the selection.
9091
*
9192
* @return {Array<object>}
93+
* @spec openspec/specs/suite-wizard/spec.md#requirement-the-wizard-must-require-at-least-one-attached-application-before-advancing-past-the-applications-step
9294
*/
9395
selected() {
9496
const applications = this.payload.applications || []
@@ -149,6 +151,7 @@ export default {
149151
*
150152
* @param {Array<{raw: object}>} options The selected NcSelect options.
151153
* @return {void}
154+
* @spec openspec/specs/suite-wizard/spec.md#requirement-the-wizard-must-require-at-least-one-attached-application-before-advancing-past-the-applications-step
152155
*/
153156
onSelectionChange(options) {
154157
const applications = (options || []).map((option) => option.raw)

src/dialogs/SuiteWizard/Step3Confirm.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export default {
5757
* The attached applications' names, for the review list.
5858
*
5959
* @return {Array<string>}
60+
* @spec openspec/specs/suite-wizard/spec.md#requirement-submitting-the-wizard-shall-create-a-suite-object-with-the-attached-applications
6061
*/
6162
applicationNames() {
6263
return summarizeApplications(this.payload.applications)

src/dialogs/SuiteWizardDialog.vue

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ export default {
8181
* Seed values for the wizard's shared stepData.
8282
*
8383
* @return {object}
84+
* @spec openspec/specs/suite-wizard/spec.md#requirement-the-wizard-shall-guide-suite-creation-through-details-application-attachment-and-confirmation-steps
8485
*/
8586
defaults() {
8687
return {
@@ -98,6 +99,7 @@ export default {
9899
* (attach existing only), confirm.
99100
*
100101
* @return {Array<{id: string, label: string}>}
102+
* @spec openspec/specs/suite-wizard/spec.md#requirement-the-wizard-shall-guide-suite-creation-through-details-application-attachment-and-confirmation-steps
101103
*/
102104
wizardSteps() {
103105
return [
@@ -191,6 +193,7 @@ export default {
191193
* reopens fresh.
192194
*
193195
* @return {void}
196+
* @spec openspec/specs/suite-wizard/spec.md#requirement-the-wizard-shall-guide-suite-creation-through-details-application-attachment-and-confirmation-steps
194197
*/
195198
onClose() {
196199
this.$emit('update:show', false)

src/modals/SaveFacetViewModal.vue

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,14 @@ export default {
8080
},
8181
8282
watch: {
83+
/**
84+
* Clear the name field each time the modal is (re)opened, so a
85+
* previous attempt's text never leaks into the next save.
86+
*
87+
* @param {boolean} value Whether the modal became visible.
88+
* @return {void}
89+
* @spec openspec/specs/gemma-faceted-search/spec.md#requirement-a-facet-selection-can-be-saved-as-a-view
90+
*/
8391
show(value) {
8492
if (value === true) {
8593
this.name = ''
@@ -89,14 +97,20 @@ export default {
8997
9098
methods: {
9199
/**
100+
* Dismiss the modal without saving.
101+
*
92102
* @return {void}
103+
* @spec openspec/specs/gemma-faceted-search/spec.md#requirement-a-facet-selection-can-be-saved-as-a-view
93104
*/
94105
closeModal() {
95106
this.$emit('close')
96107
},
97108
98109
/**
110+
* Emit the trimmed view name, refusing an empty one.
111+
*
99112
* @return {void}
113+
* @spec openspec/specs/gemma-faceted-search/spec.md#requirement-a-facet-selection-can-be-saved-as-a-view
100114
*/
101115
save() {
102116
const trimmed = this.name.trim()

0 commit comments

Comments
 (0)